Why Widgets Appear as Separate Windows

Understanding widget parenting in Qt and how to fix widgets that float outside your main window
Heads up! You've already completed this tutorial.

Sometimes when I dynamically add widgets to tabs in my PyQt6 application, they pop out as windows instead. What's going on?

If you're dynamically adding widgets to your PyQt6 application and finding that they pop out as separate floating windows instead of appearing neatly inside your application, you're running into one of Qt's gotchas: widget parenting.

This problem usually shows up when widgets are added from a callback, event listener or signal handler. But there are a million different ways to screw this up. Let's look at why this happens and how to fix it.

How Qt decides what's a window

In Qt, every widget can optionally have a parent widget. The parent determines where a widget lives visually — a widget with a parent is drawn inside that parent. A widget without a parent becomes a top-level window, floating independently on your desktop.

This is the root cause of widgets appearing outside your main window. When you create a widget and it doesn't have a parent — either because you didn't set one, or because the parent was lost somehow — Qt treats it as a standalone window.

Three ways to Get a Parent-less Widget

Here are the most common reasons widgets end up floating:

Creating widgets without a parent

python
# This widget has no parent — it will be a floating window
tabs = QTabWidget()

# This widget has a parent — it will appear inside parent_widget
tabs = QTabWidget(parent_widget)

When you add a widget to a layout, the layout assigns the parent automatically. But if something goes wrong between creation and layout insertion (like an exception, or the widget being shown prematurely), the widget stays parentless.

The safest approach is to pass a parent when creating widgets:

python
def create_new_tab(self):
    wdg = QWidget()
    layout = QGridLayout(wdg)

    tabs = QTabWidget(wdg)  # Explicitly set parent
    tab1 = QWidget(tabs)     # Explicitly set parent
    tab2 = QWidget(tabs)     # Explicitly set parent
    tabs.addTab(tab1, "Start")
    tabs.addTab(tab2, "Profile")
    layout.addWidget(tabs)

    return wdg

...although, honestly, I don't usually bother. If I know I'll be adding a widget to a layout immediately, I'll omit the parent assignment.

In an window __init__ the safety question is less relevant because, if there is an unhandled exception that blocks the adding your sub-widget to a layout, it will also block the creation of the parent window.

PyQt6 Crash Course by Martin Fitzpatrick — The important parts of PyQt6 in bite-size chunks

See the course

Accidentally recreating a widget

If you have a tab widget stored as self.w and somewhere in your code you do:

python
self.w = QTabWidget()

...the original tab widget is replaced. If the old widget gets garbage collected, all the tabs that had it as their parent suddenly become orphans — parentless widgets that float as independent windows.

Be careful not to reassign widget attributes unintentionally, especially in callbacks that might run multiple times.

Losing the parent reference

If you explicitly set a widget's parent to None, it becomes a top-level window:

python
widget.setParent(None)  # This widget is now a floating window

This sometimes happens indirectly. For example, removing a widget from a layout in certain ways can clear its parent.

A clean approach to dynamic tabs

Here's a complete, working example that dynamically adds tabs without any floating-window issues. It demonstrates the correct way to set up a QTabWidget with a "+" button that adds new tabs:

python
import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QTabWidget,
    QWidget, QVBoxLayout, QLabel
)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dynamic Tabs")
        self.setFixedSize(600, 400)

        self.tabs = QTabWidget(self)
        self.tabs.currentChanged.connect(self.on_tab_changed)

        # Add an initial tab
        self.add_content_tab("Tab 1")

        # Add the "+" tab for creating new tabs
        self.tabs.addTab(QWidget(self.tabs), "+")

        self.setCentralWidget(self.tabs)

    def on_tab_changed(self, index):
        # Check if the "+" tab was clicked
        if self.tabs.tabText(index) == "+":
            self.add_new_tab()

    def add_new_tab(self):
        # Count existing content tabs (exclude the "+" tab)
        tab_count = self.tabs.count()  # includes "+"
        new_title = f"Tab {tab_count}"

        # Insert the new tab before the "+" tab
        new_tab = self.create_tab_content(new_title)
        insert_index = self.tabs.count() - 1
        self.tabs.insertTab(insert_index, new_tab, new_title)

        # Switch to the newly created tab (avoid retriggering)
        self.tabs.blockSignals(True)
        self.tabs.setCurrentIndex(insert_index)
        self.tabs.blockSignals(False)

    def add_content_tab(self, title):
        """Add a content tab before the + tab."""
        tab = self.create_tab_content(title)
        # Insert before the last tab if "+" exists, otherwise just add
        plus_index = None
        for i in range(self.tabs.count()):
            if self.tabs.tabText(i) == "+":
                plus_index = i
                break

        if plus_index is not None:
            self.tabs.insertTab(plus_index, tab, title)
        else:
            self.tabs.addTab(tab, title)

    def create_tab_content(self, title):
        """Create the widget content for a tab."""
        widget = QWidget(self.tabs)  # Parent is the tab widget
        layout = QVBoxLayout(widget)
        label = QLabel(f"Content for {title}", widget)
        layout.addWidget(label)
        return widget


app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

A few things to notice in this example:

  • The main window inherits from QMainWindow, and QApplication is created separately.
  • Every widget is created with an explicit parent: QWidget(self.tabs), QLabel(text, widget), etc.
  • blockSignals(True) is used when programmatically changing the current tab to prevent the currentChanged signal from firing recursively.
  • New tabs are inserted before the "+" tab using insertTab, so the "+" always stays at the end.

Summary

Widget parenting is one of those things in Qt that works invisibly when everything is correct — and causes confusing visual glitches the moment something is slightly off. The good news is that once you understand the pattern, the fix is almost always the same: make sure every widget has a parent.

If you're new to PyQt6, our guide to creating your first window covers the basics of setting up a QMainWindow, while the widgets tutorial walks through the most common widgets and how to use them correctly.

The complete guide to packaging Python GUI applications with PyInstaller.
[[ discount.discount_pc ]]% OFF for the next [[ discount.duration ]] [[discount.description ]] with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses with code [[ discount.coupon_code ]]
Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

Why Widgets Appear as Separate Windows was written by Martin Fitzpatrick.

Martin Fitzpatrick is the creator of Python GUIs, and has been developing Python/Qt applications for the past 12+ years. He has written a number of popular Python books and provides Python software development & consulting for teams and startups.