16. Creating Custom Widgets
When standard Qt components cannot fulfill your requirements - particularly if you plan to reuse the widget in multiple places - you should create a custom widget rather than using ad-hoc solutions.
Qt has several ways to build custom widgets. In this chapter, we explore the two most common approaches using a tag input widget as the example:
Composition. The most convenient way is to build your widget from standard Qt components adding custom properties, signals, and slots to it. This approach is quick and easy to maintain.
Subclassing and custom painting. When you need more control, you can subclass
QWidgetdirectly and draw everything yourself usingQPainterin thepaintEvent().
16.1 Subclassing QWidget - The Minimal TagBar
The fastest way to create a custom widget is by composing existing Qt widgets. In this section we build a simple and functional TagBar that nonetheless has some drawbacks.
![]() |
You are tasked with creating tag input widget as quickly as possible. Given the tight deadline, you decide to build a custom widget using the available standard Qt widgets. |
We will design the TagBar as two cooperating classes:
- A
TagChipwidget representing a single tag pill, built from aQFramecontaining aQLabeland aQToolButton. - A
TagBarcontainer that arranges chips in aQHBoxLayoutalongside aQLineEdit. The result is fully functional - chips appear, signal their removal, and the input field accepts new tags - with no custom painting yet.
To build a custom tag bar widget:

- Create the
TagChipwidget. Start by building a single tag “pill”.TagChipis a small composite widget implemented as aQFramesubclass that contains aQLabelfor the tag text, and aQToolButtonfor the close button. Use a stylesheet to give it a rounded appearance and a matching color scheme. Clicking the Close button emits the customclosedsignal with the clicked tag text.

Create the
TagBarcontainer. Next, create theTagBarwidget. It uses a horizontal layout to arrange multipleTagChips and aQLineEditat the end, which allows the user to type and add new tags.-
Implement tag management logic.
- Maintain an internal set of tags.
- Prevent duplicate tags.
add_tag()adds a new tag on Enter.- Clicking the
TagChip’s Close button removes the tag.

- Integrate with the main editor. Finally, we connect the
Tagbarto theNoteEditorso that tags are properly saved and loaded when the user switches between notes.
Now we have a working tag bar and are able to add and remove tags, but with one major flaw: the TagBar widget does not wrap tags when their total length exceeds its lenght, it just expands.
16.2 Drawing Tags with QPainter and paintEvent()
Although the composite TagChip built from QFrame, QLabel, and QToolButton works, it difficult to achieve a perfectly balanced pill shape with consistent proportions, padding, and hover feedback across different fonts and DPI settings.
Painting the chip ourselves with QPainter gives us precise visual control, clean hover states, lower memory overhead when many tags are present, and easier future customizations.
![]() |
Due to the limitations of the composite approach you decide to replace the |
The diagram above illustrates the geometry and constants used in the painted TagChip. The main widget rectangle (dashed outer border) defines the chip’s total bounds. Inside it the filled rounded rectangle (the visible tag pill), ofset by border_width.
Text is drawn with text_padding on the left and space reserved on the right for the Close button (close_width). The Close button consists of a circular area (close_radius) positioned using close_padding. The sizeHint() calculation combines the variable text width with fixed spacing (2 * text_padding + close_width) to make sure the widget reports the correct size to layouts.
To implement a Qt widget using custom painting:

Draw the widget shape. Use
QPainterto draw a rounded rectangle that forms the pill-shaped background. Adjust the rectangle by the border width so the stroke sits neatly inside the widget bounds, giving the chip its pill appearance.Draw the widget text. Render the tag name using
drawText(). Position the text with a left padding and leave space on the right for the Close button. Align it vertically in the center.Draw the widget user interaction area (Close button). Draw a transparent circular area to mark where the close button will be, then render the
×symbol on top. At this point the button is purely visual: clicking and hovering aren’t implemented yet.
16.3 Handling Events
The painted TagChip from 16.2 looks correct but is completely unresponsive. Clicks are ignored and there is no visual feedback when hovering over the Close button. Because the × is drawn with QPainter the widget must handle its own mouse interactions and hit-testing.
![]() |
You need to add events handling to |
To make a custom-painted widget interactive:

Enable mouse tracking. Call
setMouseTracking(True)so the widget receives mouse move events even when no button is pressed.Detect hover states. Reimplement
mouseMoveEvent()to check whether the mouse is over the close button area (using the sameget_close_rect()as in painting). Update theclose_hoverflag and trigger a repaint when the state changes. Also handleenterEvent()andleaveEvent()to reset hover states.Handle clicks. Reimplement
mousePressEvent()to test if the left click occurred inside the close button rectangle. If it did, emit theclosedsignals with the tag text.
Note that, although the Close button is circular, we still use the rectangular close_rect for hit-testing. This is for practicality and performance reasons: rectangle checks (QRect.contains()) are fast and simple. The slightly larger rectangular area provides a more forgiving click target without a noticeable downside.
With this, the TagChip provides visual feedback and responds to clicks like the original composite version. However, the pill wrapping problem is still present.
16.4 Flow Layout, Size Hints, and Layout Integration
TagChip widgets are now properly painted and interactive but the pill-wrapping problem still remains. When too many tags are added, they expand the TagBar instead of wrapping to new rows. Standard layouts like QHBoxLayout cannot handle flowing/wrapping items while keeping a trailing QLineEdit.
The diagram above illustrates the core compute_rects() algorithm: a left-to right cursor advances across the chips, wrapping to a new row when the next item would ovewrflow the available width. The QLineEdit follows the same cursor after the last chip.
![]() |
To enable wrapping of |
To create a flowing tag bar:

Implement the wrapping logic. In
compute_rects(), walk through the chips from left to right, tracking a cursor position (x,y) that starts at the top-left margin. Each chip’s width comes from itssizeHint(), floored at a small minimum so very short tags don’t shrink below a usable size. Before placing a chip, check whether it would overflow the available width. If it would, and it isn’t already the first item on the current row, resetxto the margin and dropydown by one row height plus spacing. The ‘first item on the row’ check matters - without it, a single chip wider than the available width would trigger an endless wrap instead of just being on its own row. Place theQLineEditimmediately after the last chip using the same logic.Update widget positions and height. In
update_geometry(), callcompute_rects()and usesetGeometry()on every chip and the line edit. Also update theTagBars minimum height nd notify the parent layout withupdateGeometry().Coordinate with Qt’s layout system. Reimplement
resizeEvent()so the layout recalculates when the user resizes the window. ImplementsizeHint()and callupdate_geometry()inshowEvent()so the parent widget knows how tall theTagBarneeds to be.
This diagram shows how resizeEvent() and sizeHint() both go through the same compute_rects() function. We keep all geometry logic in one place so the widget behaves correctly both when resized by the user and when Qt’s layout system asks for its preferred size.
As mentioned, in addition to composition and custom painting, there are other ways to make a custom widget. A third option is specialization: subclassing an existing widget like QLabel or QPushButton and overriding just the behavior or appearance you need. Style sheets, QStyle/QProxyStyle, QGraphicsView, and item delegates are all custom-widget techniques too, covered elsewhere in this book.
