I have a QTabWidget with several tabs. Some tabs have many widgets and look fine, but tabs with only a few widgets look strange — the widgets are spread out across the entire tab. How do I keep widgets positioned at the top of a tab instead of having them stretched apart?
When building tabbed interfaces with QTabWidget, you'll often have tabs with very different amounts of content. A tab with just a couple of widgets can look a bit awkward — the widgets spread out vertically to fill the available space, leaving large gaps between them. Meanwhile, a tab packed with many widgets looks perfectly fine because the content naturally fills the area.
This is a common situation when using QVBoxLayout (or any box layout) inside your tabs. The layout distributes available space evenly among its child widgets, so with only a couple of widgets, each one gets stretched across a large portion of the tab.
The fix is simple: add a stretch to the layout. Let's walk through the problem and solution.
The problem: widgets spread across the tab
Imagine you have a QTabWidget with multiple tabs. Your first tab only has a label and a text input, while your second tab is full of widgets. Here's what that might look like in code:
import sys
from PyQt6.QtWidgets import (
QApplication,
QLabel,
QLineEdit,
QTabWidget,
QVBoxLayout,
QWidget,
)
class MainWindow(QTabWidget):
def __init__(self):
super().__init__()
# First tab — only two widgets
title = QLabel("<b>Load model file</b>")
loader = QLineEdit()
loader.setPlaceholderText("Enter file path...")
tab1 = QWidget()
layout1 = QVBoxLayout()
layout1.addWidget(title)
layout1.addWidget(loader)
tab1.setLayout(layout1)
self.addTab(tab1, "Load model")
# Second tab — many widgets
tab2 = QWidget()
layout2 = QVBoxLayout()
layout2.addWidget(QLabel("<b>Dynamic simulation</b>"))
layout2.addWidget(
QLabel(
"Use time-domain simulation to predict model evolution.\n"
"Start with defining your 2nd-order dynamics simulator here."
)
)
for i in range(6):
layout2.addWidget(QLineEdit(f"Parameter {i + 1}"))
tab2.setLayout(layout2)
self.addTab(tab2, "Run simulations")
app = QApplication(sys.argv)
window = MainWindow()
window.setMinimumSize(500, 400)
window.show()
app.exec()
If you run this, you'll see that the first tab's label and text input are spread apart vertically, floating in the middle of a large empty space. The layout is doing exactly what it's supposed to — distributing the available vertical space equally among its widgets. But visually, it looks odd. You'd probably prefer those two widgets sitting neatly at the top of the tab.
The solution: addStretch()
The QVBoxLayout (and QHBoxLayout) class has a method called addStretch(). When you add a stretch to a layout, it inserts an invisible, expandable spacer that absorbs any extra space. By placing this stretch after your widgets, you push them up to the top of the layout while the stretch fills the remaining space below.
Here's the change — just one line:
layout1 = QVBoxLayout()
layout1.addWidget(title)
layout1.addWidget(loader)
layout1.addStretch() # Pushes widgets to the top
tab1.setLayout(layout1)
The stretch acts like an invisible spring at the bottom of the layout. It expands to take up all the leftover vertical space, which keeps your actual widgets compact and positioned at the top of the tab.
Complete working example
Here's the full example with addStretch() applied to the first tab:
import sys
from PyQt6.QtWidgets import (
QApplication,
QLabel,
QLineEdit,
QTabWidget,
QVBoxLayout,
QWidget,
)
class MainWindow(QTabWidget):
def __init__(self):
super().__init__()
# First tab — only two widgets
title = QLabel("<b>Load model file</b>")
loader = QLineEdit()
loader.setPlaceholderText("Enter file path...")
tab1 = QWidget()
layout1 = QVBoxLayout()
layout1.addWidget(title)
layout1.addWidget(loader)
layout1.addStretch() # Absorbs extra space below the widgets
tab1.setLayout(layout1)
self.addTab(tab1, "Load model")
# Second tab — many widgets fill the space naturally
tab2 = QWidget()
layout2 = QVBoxLayout()
layout2.addWidget(QLabel("<b>Dynamic simulation</b>"))
layout2.addWidget(
QLabel(
"Use time-domain simulation to predict model evolution.\n"
"Start with defining your 2nd-order dynamics simulator here."
)
)
for i in range(6):
layout2.addWidget(QLineEdit(f"Parameter {i + 1}"))
tab2.setLayout(layout2)
self.addTab(tab2, "Run simulations")
app = QApplication(sys.argv)
window = MainWindow()
window.setMinimumSize(500, 400)
window.show()
app.exec()
Run this and switch between the two tabs. On the first tab, the label and text input sit snugly at the top, just as they would if there were more widgets below them. On the second tab, the widgets fill the space as before.
Placing the stretch in different positions
You can place the stretch anywhere in the layout, and the result changes accordingly:
- Stretch at the bottom (
addStretch()after all widgets) — pushes widgets to the top. - Stretch at the top (
addStretch()before all widgets) — pushes widgets to the bottom. - Stretch on both sides (one before and one after the widgets) — centers the widgets vertically.
For example, to center your widgets:
layout1 = QVBoxLayout()
layout1.addStretch() # Spring above
layout1.addWidget(title)
layout1.addWidget(loader)
layout1.addStretch() # Spring below
tab1.setLayout(layout1)
Both stretches share the extra space equally, keeping the widgets centered in the tab.
Using addStretch() with a stretch factor
The addStretch() method accepts an optional stretch factor argument. This controls how much space the stretch takes relative to other stretches in the same layout. By default the stretch factor is 0, which means the stretch takes only the space that no other widget or stretch needs.
If you have two stretches and want one to take more space than the other, you can assign different factors:
layout1 = QVBoxLayout()
layout1.addStretch(1) # Takes 1 part of available space
layout1.addWidget(title)
layout1.addWidget(loader)
layout1.addStretch(3) # Takes 3 parts of available space
tab1.setLayout(layout1)
In this case, the widgets would sit closer to the top, because the bottom stretch absorbs three times more space than the top one.
Summary
When a tab in a QTabWidget has fewer widgets than other tabs, the layout will spread those widgets across the full available space. Adding layout.addStretch() after your widgets inserts an expandable spacer that pushes the widgets to the top, giving your tab a clean, consistent look. You can place stretches before, after, or around your widgets to control vertical positioning — top-aligned, bottom-aligned, or centered.
This technique works the same way in QHBoxLayout for horizontal positioning. Wherever you have extra space in a box layout that you want to control, addStretch() is the tool to reach for. For a deeper dive into how box layouts and other layout managers work in PyQt6, see the PyQt6 layouts tutorial. If you're looking to build more complex interfaces with widgets like QLabel and QLineEdit, the PyQt6 widgets guide covers the essentials. You can also use Qt Designer to visually arrange your GUI layouts including tab widgets, which makes experimenting with stretches and spacers much easier.
Bring Your PyQt/PySide Application to Market — Specialized launch support for scientific and engineering software built using Python & Qt.