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.

  1. Launch Qt Designer and create a form based on the Dialog without Buttons template.
  2. Drag two Label widgets, two Line Edit widgets, and three Push Button widgets from the Widget Box onto the dialog.
  3. Set the text property of the labels to Name and Email Address.
  4. Set the text property of the buttons to Submit, Cancel, and Forgot Password.
  5. Because this example focuses on layout behavior, you can retain the automatically generated objectName values.
  6. Place a Vertical Spacer between the two label-and-field rows.
  7. Place another Vertical Spacer between the second input row and the Submit button.

Before applying the grid layout, the widgets may appear similar to the following arrangement:

Grid and Form Layouts in Qt5 Python

Apply Layout in a Grid to the selected widgets

  1. Select all widgets that should belong to the grid. You can hold Ctrl while clicking each widget.
  2. Right-click the selected widgets to open the context menu.
  3. Choose Layout, and then select Layout in a Grid.

Qt Designer now places the selected widgets into a QGridLayout. The grid boundaries determine which row and column each widget occupies.

Grid and Form Layouts in Qt5 Python
  1. To add more vertical separation between the Submit and Cancel buttons, insert a Vertical Spacer between their rows.
  2. To keep the Cancel and Forgot Password buttons apart, insert a Horizontal Spacer between them.

The spacers use the available room according to their size policies, producing the following layout:

Grid and Form Layouts in Qt5 Python
  1. 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:

</>
Copy
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.

</>
Copy
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:

Grid and Form Layouts in Qt5 Python

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.

  1. Launch Qt Designer and create a form based on the Dialog without Buttons template.
  2. Add two Label widgets, two Line Edit widgets, and two Push Button widgets.
  3. Set the label text to Name and Email Address.
  4. Set the button text to Cancel and Submit.
  5. You may retain the default objectName values because the example concentrates on layout generation.

Before applying the form layout, the interface may look like this:

Grid and Form Layouts in Qt5 Python

Apply Layout in a Form Layout to the widgets

  1. Select all widgets that should be managed by the form layout.
  2. Right-click the selection and open the Layout submenu.
  3. 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.

Grid and Form Layouts in Qt5 Python
  1. Insert a Vertical Spacer between the Name and Email Address rows when additional separation is required.
  2. Insert another Vertical Spacer between the Email Address row and the buttons.
  3. 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:

Grid and Form Layouts in Qt5 Python
  1. 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:

</>
Copy
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.

</>
Copy
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:

Grid and Form Layouts in Qt5 Python

QGridLayout vs QFormLayout in PyQt5

LayoutBest suited forWidget placementSpanning support
QGridLayoutDashboards, keypads, control panels, and complex multi-column formsExplicit row and column coordinatesWidgets can span multiple rows or columns
QFormLayoutSettings pages and label-and-field data-entry formsRows with label, field, or spanning rolesA 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().

</>
Copy
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.

</>
Copy
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 QLineEdit in the same grid row or form row.
  • Verify that the Submit button uses the intended column span in the QGridLayout example.
  • 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 .ui files 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.