Dynamic Layout with QSpacerItem in PyQt6

Recently, I was working on a PyQt6 application where I needed to create a responsive UI that would adapt to different window sizes. The challenge was to keep elements properly aligned and spaced regardless of how the user resized the window. That’s when I discovered the power of QSpacerItem in PyQt6.

In this article, I will share how QSpacerItem can solve your layout problems and create truly dynamic interfaces. I’ll cover different approaches to implement spacers effectively and show you real-world examples you can use in your projects.

Let us start..!

QSpacerItem in PyQt6

QSpacerItem is a layout item that creates space within layouts. Think of it as an invisible, stretchable component that pushes widgets apart or aligns them properly within your application window.

Unlike visible widgets, spacers don’t display anything on screen, they simply consume space to help position other elements.

1. Create Horizontal Spacers

Let’s start with a basic example of using horizontal spacers to center a widget in a layout:

import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QHBoxLayout, 
                            QPushButton, QSpacerItem, QSizePolicy)

class HorizontalSpacerDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Horizontal Spacer Demo")
        self.resize(600, 100)

        # Create horizontal layout
        layout = QHBoxLayout()
        self.setLayout(layout)

        # Add a spacer on the left
        layout.addSpacerItem(QSpacerItem(40, 20, 
                            QSizePolicy.Policy.Expanding, 
                            QSizePolicy.Policy.Minimum))

        # Add a button in the middle
        layout.addWidget(QPushButton("Centered Button"))

        # Add a spacer on the right
        layout.addSpacerItem(QSpacerItem(40, 20, 
                            QSizePolicy.Policy.Expanding, 
                            QSizePolicy.Policy.Minimum))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = HorizontalSpacerDemo()
    window.show()
    sys.exit(app.exec())

I executed the above example code and added the screenshot below.

Layout with QSpacerItem in PyQt6

In this example, I’ve added spacers on both sides of a button. The spacers have an expanding horizontal policy, which means they’ll grow equally as the window expands, keeping the button centered.

The parameters for QSpacerItem are:

  • Width hint (40 pixels)
  • Height hint (20 pixels)
  • Horizontal size policy (Expanding)
  • Vertical size policy (Minimum)

Read QTreeView Widget in PyQt6

2. Create Vertical Spacers

Vertical spacers work similarly but control spacing in the vertical direction:

import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, 
                            QPushButton, QSpacerItem, QSizePolicy)

class VerticalSpacerDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Vertical Spacer Demo")
        self.resize(300, 400)

        # Create vertical layout
        layout = QVBoxLayout()
        self.setLayout(layout)

        # Add a spacer at the top
        layout.addSpacerItem(QSpacerItem(20, 40, 
                            QSizePolicy.Policy.Minimum, 
                            QSizePolicy.Policy.Expanding))

        # Add a button in the middle
        layout.addWidget(QPushButton("Vertically Centered"))

        # Add a spacer at the bottom
        layout.addSpacerItem(QSpacerItem(20, 40, 
                            QSizePolicy.Policy.Minimum, 
                            QSizePolicy.Policy.Expanding))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = VerticalSpacerDemo()
    window.show()
    sys.exit(app.exec())

I executed the above example code and added the screenshot below.

Dynamic Layout with QSpacerItem in PyQt6

Notice that with vertical spacers, I’ve swapped the size policies: the vertical policy is now Expanding while the horizontal is Minimum.

3. Fixed vs. Expanding Spacers

Sometimes you need fixed spacing rather than expanding space. Here’s how to create both types:

import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QHBoxLayout, 
                            QPushButton, QSpacerItem, QSizePolicy, QLabel)

class SpacerTypesDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Fixed vs Expanding Spacers")
        self.resize(600, 100)

        # Create horizontal layout
        layout = QHBoxLayout()
        self.setLayout(layout)

        # Add first button
        layout.addWidget(QPushButton("Left Button"))

        # Add a fixed spacer (40px wide, won't expand)
        layout.addSpacerItem(QSpacerItem(40, 20, 
                            QSizePolicy.Policy.Fixed, 
                            QSizePolicy.Policy.Minimum))

        # Add a label
        layout.addWidget(QLabel("Fixed 40px gap →"))

        # Add an expanding spacer (grows with window)
        layout.addSpacerItem(QSpacerItem(10, 20, 
                            QSizePolicy.Policy.Expanding, 
                            QSizePolicy.Policy.Minimum))

        # Add a label
        layout.addWidget(QLabel("← Expanding gap"))

        # Add last button
        layout.addWidget(QPushButton("Right Button"))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = SpacerTypesDemo()
    window.show()
    sys.exit(app.exec())

I executed the above example code and added the screenshot below.

QSpacerItem in PyQt6

The key difference is in the size policy:

  • QSizePolicy.Policy.Fixed creates a spacer with a fixed size
  • QSizePolicy.Policy.Expanding creates a spacer that grows/shrinks with the window

4. Create Responsive Forms with Spacers

One common UI pattern is a form that needs to stay properly aligned. Let’s create a login form using spacers:

import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
                            QLabel, QLineEdit, QPushButton, QSpacerItem, 
                            QSizePolicy)

class LoginFormDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Login Form with Spacers")
        self.resize(400, 200)

        # Main layout
        main_layout = QVBoxLayout()
        self.setLayout(main_layout)

        # Add spacer at top
        main_layout.addSpacerItem(QSpacerItem(20, 20, 
                                QSizePolicy.Policy.Minimum, 
                                QSizePolicy.Policy.Expanding))

        # Username row
        username_layout = QHBoxLayout()
        username_layout.addWidget(QLabel("Username:"))
        username_layout.addWidget(QLineEdit())
        main_layout.addLayout(username_layout)

        # Password row
        password_layout = QHBoxLayout()
        password_layout.addWidget(QLabel("Password:"))
        password_layout.addWidget(QLineEdit())
        main_layout.addLayout(password_layout)

        # Button row with spacers for centering
        button_layout = QHBoxLayout()
        button_layout.addSpacerItem(QSpacerItem(40, 20, 
                                    QSizePolicy.Policy.Expanding, 
                                    QSizePolicy.Policy.Minimum))
        button_layout.addWidget(QPushButton("Login"))
        button_layout.addSpacerItem(QSpacerItem(40, 20, 
                                    QSizePolicy.Policy.Expanding, 
                                    QSizePolicy.Policy.Minimum))
        main_layout.addLayout(button_layout)

        # Add spacer at bottom
        main_layout.addSpacerItem(QSpacerItem(20, 20, 
                                QSizePolicy.Policy.Minimum, 
                                QSizePolicy.Policy.Expanding))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = LoginFormDemo()
    window.show()
    sys.exit(app.exec())

This form demonstrates vertical spacers at the top and bottom to center the form vertically, plus horizontal spacers to center the button.

Check out Create Alert Dialogs with QMessageBox in PyQt6

5. Use addStretch() as a Shortcut

While QSpacerItem gives you precise control, PyQt6 layouts also offer a convenient shortcut called addStretch() method in Python:

import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QHBoxLayout, 
                            QPushButton)

class StretchDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Using addStretch()")
        self.resize(500, 100)

        # Create horizontal layout
        layout = QHBoxLayout()
        self.setLayout(layout)

        # Add a button on the left
        layout.addWidget(QPushButton("Left"))

        # Add a stretch (equivalent to an expanding spacer)
        layout.addStretch(1)  # The parameter is the stretch factor

        # Add a button on the right
        layout.addWidget(QPushButton("Right"))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = StretchDemo()
    window.show()
    sys.exit(app.exec())

The addStretch(1) method is equivalent to adding an expanding spacer. The parameter (1) represents the stretch factor, which determines how much space this stretch will take relative to other stretches or expandable widgets.

Create a Real-World Dashboard Layout

Let’s build a more complex example – a dashboard with multiple sections that uses spacers to create a balanced layout:

import sys
from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
                           QPushButton, QLabel, QFrame, QSpacerItem, 
                           QSizePolicy)
from PyQt6.QtCore import Qt

class DashboardDemo(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dashboard with Spacers")
        self.resize(800, 600)
        
        # Main layout
        main_layout = QVBoxLayout()
        self.setLayout(main_layout)
        
        # Header section
        header = QFrame()
        header.setFrameShape(QFrame.Shape.StyledPanel)
        header.setMinimumHeight(80)
        header_layout = QHBoxLayout(header)
        header_layout.addWidget(QLabel("US Sales Dashboard"))
        header_layout.addSpacerItem(QSpacerItem(40, 20, 
                                   QSizePolicy.Policy.Expanding, 
                                   QSizePolicy.Policy.Minimum))
        header_layout.addWidget(QPushButton("Refresh"))
        header_layout.addWidget(QPushButton("Sign Out"))
        main_layout.addWidget(header)
        
        # Content section
        content = QHBoxLayout()
        
        # Left sidebar
        sidebar = QFrame()
        sidebar.setFrameShape(QFrame.Shape.StyledPanel)
        sidebar.setMinimumWidth(150)
        sidebar_layout = QVBoxLayout(sidebar)
        sidebar_layout.addWidget(QPushButton("Dashboard"))
        sidebar_layout.addWidget(QPushButton("Sales Reports"))
        sidebar_layout.addWidget(QPushButton("Customer Data"))
        sidebar_layout.addWidget(QPushButton("Analytics"))
        sidebar_layout.addSpacerItem(QSpacerItem(20, 40, 
                                   QSizePolicy.Policy.Minimum, 
                                   QSizePolicy.Policy.Expanding))
        content.addWidget(sidebar)
        
        # Main content area
        main_content = QFrame()
        main_content.setFrameShape(QFrame.Shape.StyledPanel)
        main_content_layout = QVBoxLayout(main_content)
        
        # Top stats row
        stats_row = QHBoxLayout()
        
        # Create 3 stat boxes with equal spacing
        for region in ["West Region", "Central Region", "East Region"]:
            stat_box = QFrame()
            stat_box.setFrameShape(QFrame.Shape.StyledPanel)
            stat_box.setMinimumHeight(100)
            stat_layout = QVBoxLayout(stat_box)
            stat_layout.addWidget(QLabel(f"{region}"))
            stat_layout.addWidget(QLabel("$1,234,567"))
            stat_layout.addWidget(QLabel("+12.3% YoY"))
            stats_row.addWidget(stat_box)
            # Add spacer between boxes (but not after the last one)
            if region != "East Region":
                stats_row.addSpacerItem(QSpacerItem(20, 20, 
                                       QSizePolicy.Policy.Fixed, 
                                       QSizePolicy.Policy.Minimum))
                
        main_content_layout.addLayout(stats_row)
        
        # Add spacer between sections
        main_content_layout.addSpacerItem(QSpacerItem(20, 20, 
                                         QSizePolicy.Policy.Minimum, 
                                         QSizePolicy.Policy.Fixed))
        
        # Chart section
        chart_section = QFrame()
        chart_section.setFrameShape(QFrame.Shape.StyledPanel)
        chart_section.setMinimumHeight(300)
        chart_layout = QVBoxLayout(chart_section)
        chart_layout.addWidget(QLabel("Sales Performance Chart"))
        chart_layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
        main_content_layout.addWidget(chart_section)
        
        content.addWidget(main_content)
        
        # Add content to main layout
        main_layout.addLayout(content)
        
        # Footer
        footer = QFrame()
        footer.setFrameShape(QFrame.Shape.StyledPanel)
        footer.setMinimumHeight(30)
        footer_layout = QHBoxLayout(footer)
        footer_layout.addWidget(QLabel("© 2025 US Sales Dashboard"))
        footer_layout.addSpacerItem(QSpacerItem(40, 20, 
                                   QSizePolicy.Policy.Expanding, 
                                   QSizePolicy.Policy.Minimum))
        footer_layout.addWidget(QLabel("Version 1.0.2"))
        main_layout.addWidget(footer)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = DashboardDemo()
    window.show()
    sys.exit(app.exec())

This comprehensive dashboard example demonstrates how spacers can create balanced layouts in a realistic application. Notice how I’ve used:

  1. Horizontal spacers in the header to push buttons to the right
  2. Vertical spacers in the sidebar to push content to the top
  3. Fixed spacers between stat boxes to create equal spacing
  4. Expanding spacers in the footer to separate copyright from version info

In this article, I have explained how to create a dynamic layout with QSpacerItem in PyQt6. I discussed five methods to accomplish this task they are: creating horizontal spacers, creating vertical spacers, fixed vs. expanding spacers, creating responsive forms with spacers, and using addStrech() as a shortcut.

PyQt6-related tutorials.

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.