2025, Oct 03 17:00

How to Center a PyQt5 Widget in a QVBoxLayout: Avoid AlignBottom Pitfalls and Use addStretch

Learn why setAlignment() won’t move widgets in PyQt5 layouts and how to center them in a QVBoxLayout using addStretch. Fix AlignBottom when hiding widgets.

Centering a widget in a PyQt5 layout can be counterintuitive if you mix layout-level alignments and widget-level alignment calls. A common scenario: a label is added to a QVBoxLayout with AlignBottom, then other widgets are hidden, and the label stubbornly sticks to the bottom. The goal is to center the label on the page after the button is clicked.

Problem setup

Below is a minimal version of the guessing game where the label stays at the bottom after you press the button, even though we try to realign it:

import sys
import time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class AppWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        QFontDatabase.addApplicationFont("fonts\Evangelie.ttf")
        QFontDatabase.addApplicationFont("fonts\Bebas_Neue_Cyrillic.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Medium.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Bold.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-SemiBold.ttf")
        self.msg_label = QLabel("Скільки чулувіків має крапка?", self)
        self.input_field = QLineEdit(self)
        self.submit_btn = QPushButton("Відповісти.", self)
        self.build_ui()
    def build_ui(self):
        host = QWidget()
        self.setCentralWidget(host)
        self.setWindowTitle("Guess.")
        self.resize(800, 700)
        self.setStyleSheet("background-color: #697565;")
        self.msg_label.setFont(QFont("Montserrat Alternates SemiBold", 24))
        self.msg_label.adjustSize()
        self.msg_label.setStyleSheet(
            "margin: 0, 20, 0, 0;"
            "color: #ECDFCC;"
            "font-style: bold;"
        )
        self.input_field.setFixedSize(200, 80)
        self.input_field.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.input_field.setFont(QFont("Montserrat Alternates SemiBold", 20))
        self.input_field.setStyleSheet(
            "background-color: #181C14;"
            "color: #697565;"
            "border-radius: 15px;"
            "margin: 0,20,0,0;"
        )
        self.submit_btn.setStyleSheet(
            "border-radius:15px;"
            "background-color: #ECDFCC;"
            "color: #181C14;"
        )
        self.submit_btn.setFixedSize(150, 50)
        self.submit_btn.setFont(QFont("Montserrat Alternates Medium", 10))
        self.submit_btn.clicked.connect(self.handle_click)
        layout_main = QVBoxLayout()
        layout_main.addWidget(self.msg_label, alignment=Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignJustify)
        layout_main.addWidget(self.input_field, alignment=Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignJustify)
        layout_main.addWidget(self.submit_btn, alignment=Qt.AlignmentFlag.AlignJustify | Qt.AlignmentFlag.AlignTop)
        host.setLayout(layout_main)
    def handle_click(self):
        self._user_value = self.input_field.text()
        print(self._user_value)
        if self._user_value == '0':
            self.msg_label.setText("You won!")
            self.input_field.hide()
            self.submit_btn.hide()
            self.msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
    def center_window(self):
        frame_geo = self.frameGeometry()
        screen_center = QDesktopWidget().availableGeometry().center()
        frame_geo.moveCenter(screen_center)
        self.move(frame_geo.topLeft())
def main():
    app = QApplication(sys.argv)
    ui = AppWindow()
    ui.show()
    sys.exit(app.exec_())
if __name__ == "__main__":
    main()

Why the label won’t move to the center

The vertical placement is dictated by the alignment used when the widget is added to the layout. The label was added with AlignBottom, so it keeps its vertical slot near the bottom. Calling setAlignment() on the label doesn’t change its position in the layout; it only affects the alignment of the label’s content.

Fix: use stretches to center the stack

To make the label sit in the center of the window after the button is pressed, place flexible space above and below the widget stack. In a QVBoxLayout, addStretch() creates expandable space. Adding one before and after your widgets keeps the content centered vertically. With that in place, horizontal alignment is all you need.

import sys
import time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class AppWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        QFontDatabase.addApplicationFont("fonts\Evangelie.ttf")
        QFontDatabase.addApplicationFont("fonts\Bebas_Neue_Cyrillic.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Medium.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-Bold.ttf")
        QFontDatabase.addApplicationFont("fonts\MontserratAlternates-SemiBold.ttf")
        self.msg_label = QLabel("Скільки чулувіків має крапка?", self)
        self.input_field = QLineEdit(self)
        self.submit_btn = QPushButton("Відповісти.", self)
        self.build_ui()
    def build_ui(self):
        host = QWidget()
        self.setCentralWidget(host)
        self.setWindowTitle("Guess.")
        self.resize(800, 700)
        self.setStyleSheet("background-color: #697565;")
        self.msg_label.setFont(QFont("Montserrat Alternates SemiBold", 24))
        self.msg_label.adjustSize()
        self.msg_label.setStyleSheet(
            "margin: 0, 20, 0, 0;"
            "color: #ECDFCC;"
            "font-style: bold;"
        )
        self.input_field.setFixedSize(200, 80)
        self.input_field.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.input_field.setFont(QFont("Montserrat Alternates SemiBold", 20))
        self.input_field.setStyleSheet(
            "background-color: #181C14;"
            "color: #697565;"
            "border-radius: 15px;"
            "margin: 0,20,0,0;"
        )
        self.submit_btn.setStyleSheet(
            "border-radius:15px;"
            "background-color: #ECDFCC;"
            "color: #181C14;"
        )
        self.submit_btn.setFixedSize(150, 50)
        self.submit_btn.setFont(QFont("Montserrat Alternates Medium", 10))
        self.submit_btn.clicked.connect(self.handle_click)
        layout_main = QVBoxLayout()
        layout_main.addStretch()
        layout_main.addWidget(self.msg_label, alignment=Qt.AlignmentFlag.AlignHCenter)
        layout_main.addWidget(self.input_field, alignment=Qt.AlignmentFlag.AlignHCenter)
        layout_main.addWidget(self.submit_btn, alignment=Qt.AlignmentFlag.AlignHCenter)
        layout_main.addStretch()
        host.setLayout(layout_main)
    def handle_click(self):
        self._user_value = self.input_field.text()
        print(self._user_value)
        if self._user_value == '0':
            self.msg_label.setText("You won!")
            self.input_field.hide()
            self.submit_btn.hide()
            self.msg_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
    def center_window(self):
        frame_geo = self.frameGeometry()
        screen_center = QDesktopWidget().availableGeometry().center()
        frame_geo.moveCenter(screen_center)
        self.move(frame_geo.topLeft())
def main():
    app = QApplication(sys.argv)
    ui = AppWindow()
    ui.show()
    sys.exit(app.exec_())
if __name__ == "__main__":
    main()

Why this is worth knowing

Subtle layout flags easily produce surprising UI behavior. By keeping the vertical balance with addStretch() and using simple horizontal alignment per widget, you avoid fighting the layout system later, especially when dynamically showing or hiding controls. Also keep in mind a practical path tip for Python strings: a single backslash is an escape character. If you choose backslashes in paths, use double backslashes or raw strings; otherwise, forward slashes are broadly accepted by Qt and are cross-platform friendly.

Conclusion

When you want a widget to move to the center after UI changes, don’t try to force it with widget-level alignment alone. Let the layout do the heavy lifting: surround your content with stretches for vertical centering and keep per-widget alignment simple with AlignHCenter. This pattern keeps your interfaces predictable when elements are hidden, shown, or replaced.

The article is based on a question from StackOverflow by Krapka and an answer by bruno.