Qt layout managers arrange widgets dynamically when a window is displayed or resized. In this tutorial, you will use QGridLayout and QFormLayout in PyQt5 to create two responsive data-entry forms with Qt Designer.
A grid layout is suitable when widgets must occupy specific rows and columns or span multiple cells. A form layout is designed for label-and-field interfaces, where each row commonly contains a descriptive label and an input widget.
Creating a PyQt5 Grid Layout in Qt Designer
QGridLayout arranges widgets in a stretchable grid of rows and columns. Unlike fixed widget coordinates, the layout recalculates widget sizes and positions when the parent window changes size.
Build the sign-in interface for QGridLayout
In this example, you will create a sign-in form containing two labels, two line-edit fields, and three buttons. The Submit button will span the width of the form, while the Cancel and Forgot Password buttons will share the final row.
- Launch Qt Designer and create a form based on the
Dialog without Buttonstemplate. - Drag two
Labelwidgets, twoLine Editwidgets, and threePush Buttonwidgets from the Widget Box onto the dialog. - Set the
textproperty of the labels toNameandEmail Address. - Set the
textproperty of the buttons toSubmit,Cancel, andForgot Password. - Because this example focuses on layout behavior, you can retain the automatically generated
objectNamevalues. - Place a
Vertical Spacerbetween the two label-and-field rows. - Place another
Vertical Spacerbetween the second input row and the Submit button.
Before applying the grid layout, the widgets may appear similar to the following arrangement:

Apply Layout in a Grid to the selected widgets
- Select all widgets that should belong to the grid. You can hold
Ctrlwhile clicking each widget. - Right-click the selected widgets to open the context menu.
- Choose
Layout, and then selectLayout in a Grid.
Qt Designer now places the selected widgets into a QGridLayout. The grid boundaries determine which row and column each widget occupies.

- To add more vertical separation between the Submit and Cancel buttons, insert a
Vertical Spacerbetween their rows. - To keep the Cancel and Forgot Password buttons apart, insert a
Horizontal Spacerbetween them.
The spacers use the available room according to their size policies, producing the following layout:

- Save the interface as
demoGridLayout.ui.
Convert the QGridLayout UI file to Python
Qt Designer saves the interface in a .ui XML file. To generate a Python module from this file, open a Command Prompt, move to the directory containing the file, and run the following existing command:
C:PyQt5>pyuic5 demoGridLayout.ui -o demoGridLayout.py
The generated demoGridLayout.py file may contain the following code:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(369, 279)
self.widget = QtWidgets.QWidget(Dialog)
self.widget.setGeometry(QtCore.QRect(20, 31, 276, 216))
self.widget.setObjectName("widget")
self.gridLayout = QtWidgets.QGridLayout(self.widget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setObjectName("gridLayout")
self.pushButton = QtWidgets.QPushButton(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.pushButton.setFont(font)
self.pushButton.setObjectName("pushButton")
self.gridLayout.addWidget(self.pushButton, 4, 0, 1, 5)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.
QSizePolicy.Minimum,QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem, 5, 0, 1, 1)
self.label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.label.setFont(font)
self.label.setObjectName("label")
self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
self.label_2 = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.label_2.setFont(font)
self.label_2.setObjectName("label_2")
self.gridLayout.addWidget(self.label_2, 2, 0, 1, 2)
self.lineEdit_2 = QtWidgets.QLineEdit(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.lineEdit_2.setFont(font)
self.lineEdit_2.setObjectName("lineEdit_2")
self.gridLayout.addWidget(self.lineEdit_2, 2, 2, 1, 3)
self.lineEdit = QtWidgets.QLineEdit(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.lineEdit.setFont(font)
self.lineEdit.setObjectName("lineEdit")
self.gridLayout.addWidget(self.lineEdit, 0, 2, 1, 3)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.
QSizePolicy.Minimum,QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem1, 3, 1, 1, 1)
spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.
QSizePolicy.Minimum,QtWidgets.QSizePolicy.Expanding)
self.gridLayout.addItem(spacerItem2, 1, 2, 1, 3)
self.pushButton_2 = QtWidgets.QPushButton(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.pushButton_2.setFont(font)
self.pushButton_2.setObjectName("pushButton_2")
self.gridLayout.addWidget(self.pushButton_2, 6, 0, 1, 3)
self.pushButton_3 = QtWidgets.QPushButton(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.pushButton_3.setFont(font)
self.pushButton_3.setObjectName("pushButton_3")
self.gridLayout.addWidget(self.pushButton_3, 6, 4, 1, 1)
spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.
QSizePolicy.Expanding,QtWidgets.QSizePolicy.Minimum)
self.gridLayout.addItem(spacerItem3, 6, 3, 1, 1)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.pushButton.setText(_translate("Dialog", "Submit"))
self.label.setText(_translate("Dialog", "Name"))
self.label_2.setText(_translate("Dialog", "Email Address"))
self.pushButton_2.setText(_translate("Dialog", "Cancel"))
self.pushButton_3.setText(_translate("Dialog",
"Forgot Password"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
How QGridLayout row, column, and span values work
The generated code creates a QGridLayout named gridLayout. Each call to addWidget() specifies where a widget belongs in the grid.
grid_layout.addWidget(widget, row, column, row_span, column_span)
For example, addWidget(self.pushButton, 4, 0, 1, 5) places the Submit button at row 4 and column 0. It occupies one row and spans five columns. The Name and Email Address fields also span multiple columns so that the editable area receives more horizontal space than its label.
Grid coordinates are zero-based. Adding a widget to row 0, column 0 places it in the first cell. Rows and columns do not need to be declared in advance; the layout creates them as widgets and spacer items are inserted.
The QSpacerItem objects use expanding size policies. A vertical spacer can absorb extra height, while a horizontal spacer can absorb extra width. This is why the controls remain separated when the dialog grows.
When the application runs, the labels, line edits, and buttons appear in a responsive grid:

Creating a PyQt5 Form Layout in Qt Designer
QFormLayout is intended for forms that pair descriptive labels with input controls. It normally creates a label column and a field column, making it appropriate for settings screens, account details, contact forms, and similar data-entry interfaces.
Unlike a general-purpose grid, a form layout assigns widgets to semantic roles. The most commonly used roles are LabelRole, FieldRole, and SpanningRole.
Build the label-and-field interface for QFormLayout
This example creates two input rows for a name and email address. A final row contains Cancel and Submit buttons.
- Launch Qt Designer and create a form based on the
Dialog without Buttonstemplate. - Add two
Labelwidgets, twoLine Editwidgets, and twoPush Buttonwidgets. - Set the label text to
NameandEmail Address. - Set the button text to
CancelandSubmit. - You may retain the default
objectNamevalues because the example concentrates on layout generation.
Before applying the form layout, the interface may look like this:

Apply Layout in a Form Layout to the widgets
- Select all widgets that should be managed by the form layout.
- Right-click the selection and open the
Layoutsubmenu. - Select
Layout in a Form Layout.
Qt Designer assigns the labels to the left-side label role and the line edits to the right-side field role.

- Insert a
Vertical Spacerbetween the Name and Email Address rows when additional separation is required. - Insert another
Vertical Spacerbetween the Email Address row and the buttons. - Select the form layout boundary and increase its height. The expanding spacers will use the additional vertical room.
The adjusted form layout appears as follows:

- Save the interface as
demoFormLayout.ui.
Convert the QFormLayout UI file to Python
Open a Command Prompt in the directory containing demoFormLayout.ui, and run the following existing command:
C:PyQt5>pyuic5 demoFormLayout.ui -o demoFormLayout.py
The generated demoFormLayout.py module may contain the following code:
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(407, 211)
self.widget = QtWidgets.QWidget(Dialog)
self.widget.setGeometry(QtCore.QRect(20, 30, 276, 141))
self.widget.setObjectName("widget")
self.formLayout = QtWidgets.QFormLayout(self.widget)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setObjectName("formLayout")
self.label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.label.setFont(font)
self.label.setObjectName("label")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.
LabelRole,self.label)
self.lineEdit = QtWidgets.QLineEdit(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.lineEdit.setFont(font)
self.lineEdit.setObjectName("lineEdit")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.
FieldRole,self.lineEdit)
self.label_2 = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.label_2.setFont(font)
self.label_2.setObjectName("label_2")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.
LabelRole,self.label_2)
self.lineEdit_2 = QtWidgets.QLineEdit(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.lineEdit_2.setFont(font)
self.lineEdit_2.setObjectName("lineEdit_2")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.
FieldRole, self.lineEdit_2)
self.pushButton_2 = QtWidgets.QPushButton(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.pushButton_2.setFont(font)
self.pushButton_2.setObjectName("pushButton_2")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.
LabelRole,self.pushButton_2)
self.pushButton = QtWidgets.QPushButton(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.pushButton.setFont(font)
self.pushButton.setObjectName("pushButton")
self.formLayout.setWidget(4, QtWidgets.QFormLayout.
FieldRole,self.pushButton)
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.
QSizePolicy.Minimum,QtWidgets.QSizePolicy.Expanding)
self.formLayout.setItem(1, QtWidgets.QFormLayout.FieldRole,
spacerItem)
spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.
QSizePolicy.Minimum,QtWidgets.QSizePolicy.Expanding)
self.formLayout.setItem(3, QtWidgets.QFormLayout.FieldRole,
spacerItem1)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.label.setText(_translate("Dialog", "Name"))
self.label_2.setText(_translate("Dialog", "Email Address"))
self.pushButton_2.setText(_translate("Dialog", "Cancel"))
self.pushButton.setText(_translate("Dialog", "Submit"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
How QFormLayout label and field roles work
The generated code creates a QFormLayout named formLayout. It then uses setWidget() to assign each control to a row and role.
form_layout.setWidget(row, QFormLayout.LabelRole, label_widget)
form_layout.setWidget(row, QFormLayout.FieldRole, field_widget)
For the first row, self.label is assigned to LabelRole, while self.lineEdit is assigned to FieldRole. The same pattern is used for the email address row. The buttons are also assigned to the two available roles in their row.
The spacer items are inserted into intermediate rows with setItem(). Because the spacers use an expanding vertical size policy, they absorb extra height when the form becomes taller.
When the application runs, the controls appear in the following two-column form:

QGridLayout vs QFormLayout in PyQt5
| Layout | Best suited for | Widget placement | Spanning support |
|---|---|---|---|
QGridLayout | Dashboards, keypads, control panels, and complex multi-column forms | Explicit row and column coordinates | Widgets can span multiple rows or columns |
QFormLayout | Settings pages and label-and-field data-entry forms | Rows with label, field, or spanning roles | A row can use a spanning widget when required |
Choose QGridLayout when exact cell placement and spanning behavior are central to the interface. Choose QFormLayout when the interface mainly consists of descriptive labels paired with input widgets.
Managing spacing, margins, and resizing in Qt layouts
Layouts manage geometry, but their appearance also depends on margins, spacing, stretch factors, and widget size policies.
- Contents margins: Control the empty area between the layout and the edge of its parent widget.
- Layout spacing: Controls the distance between neighboring layout items.
- Spacer items: Reserve or absorb space within a row or column.
- Size policies: Tell the layout whether a widget should remain fixed, use its preferred size, or expand.
- Row and column stretch: Determine which grid rows or columns receive additional space.
For interfaces created in Python rather than Qt Designer, these properties can be adjusted through methods such as setContentsMargins(), setSpacing(), setRowStretch(), and setColumnStretch().
grid_layout.setContentsMargins(12, 12, 12, 12)
grid_layout.setHorizontalSpacing(10)
grid_layout.setVerticalSpacing(8)
grid_layout.setColumnStretch(0, 0)
grid_layout.setColumnStretch(1, 1)
In this example, the second column receives the available extra width, which is useful when that column contains editable fields.
Running Qt Designer UI files without generating Python code
Generating a Python module with pyuic5 is one approach. PyQt5 can also load a .ui file at runtime with uic.loadUi(). This can be useful while the interface is still changing because you do not need to regenerate the Python file after every visual edit.
import sys
from PyQt5 import QtWidgets, uic
app = QtWidgets.QApplication(sys.argv)
dialog = uic.loadUi("demoGridLayout.ui")
dialog.show()
sys.exit(app.exec_())
For either workflow, keep application logic in a separate class or module rather than editing a generated pyuic5 file directly. Regenerating that file can overwrite manual changes.
Common PyQt5 layout problems
Widgets do not resize with the dialog
Applying a layout only to a group of child widgets is not always enough. The containing widget or dialog must also have an appropriate top-level layout. In Qt Designer, select the parent form and apply a layout so that the inner layout can expand with the window.
A grid widget appears in the wrong cell
Check the row and column arguments passed to addWidget(). Also check the row-span and column-span values, because a widget spanning several cells may overlap the area where another widget was intended to appear.
Form labels and fields are paired incorrectly
Verify that the label and its input widget use the same row number and the correct LabelRole and FieldRole values.
Large empty gaps appear between controls
Inspect any expanding spacers and stretch factors. An expanding spacer intentionally consumes available space, so replace it with a fixed spacer or adjust its size policy when a large flexible gap is not required.
Frequently asked questions about PyQt5 grid and form layouts
What is QGridLayout in PyQt5?
QGridLayout is a layout manager that arranges widgets in rows and columns. A widget can occupy one cell or span several rows and columns.
What is QFormLayout used for in PyQt5?
QFormLayout is used for interfaces containing label-and-field pairs. Each row can hold a label and an input widget, or a single widget that spans the row.
How do I make a widget span columns in QGridLayout?
Pass row-span and column-span values to addWidget(). For example, layout.addWidget(button, 0, 0, 1, 2) places the button in row 0 and makes it span two columns.
Why does a PyQt5 layout not fill the window?
The inner widgets may be arranged correctly while their parent still uses fixed geometry. Apply a top-level layout to the dialog or central widget, and check the widgets’ size policies and layout margins.
Should I edit the Python file generated by pyuic5?
It is safer not to edit generated files because running pyuic5 again can overwrite your changes. Subclass the generated UI class, use composition, or load the .ui file at runtime and keep application logic separately.
PyQt5 grid and form layout editorial QA checklist
- Confirm that every label is paired with the intended
QLineEditin the same grid row or form row. - Verify that the Submit button uses the intended column span in the
QGridLayoutexample. - Resize both dialogs and confirm that controls expand or remain fixed according to their size policies.
- Check that vertical and horizontal spacers create useful separation without producing excessive empty space.
- Regenerate the Python files from the saved
.uifiles and confirm that both examples launch without missing-widget or layout errors.
QGridLayout and QFormLayout both remove the need to maintain fixed widget coordinates. The grid layout provides precise row-and-column control, while the form layout provides a direct structure for label-and-field interfaces.
TutorialKart.com