How can I create a menu item with a custom background color using QWidgetAction, while keeping the same behavior and style as regular QAction items?
When building menus in PyQt6, you might want to visually distinguish certain menu items — for example, by giving one a different background color. The standard QAction class doesn't directly support styling with stylesheets, so the usual approach is to use QWidgetAction, which lets you embed any widget inside a menu. The challenge is making that custom widget look and behave like a normal menu item.
Let's walk through how to do this properly.
The problem with a basic approach
A first attempt might look like embedding a QCheckBox inside a QWidgetAction and applying a stylesheet to it:
check_box = QCheckBox("My Item")
check_box.setStyleSheet("background-color: tomato;")
widget_action = QWidgetAction(self)
widget_action.setDefaultWidget(check_box)
This works partially — you get a checkbox in the menu with a red background. But the styling only covers the checkbox widget itself, not the full width of the menu item. The result looks awkward: the colored area doesn't stretch across the entire row, and the hover/highlight behavior doesn't match the other items.
Using a container widget for full-width styling
To get the background color to fill the entire menu item row, you need to wrap your widget inside a container (like a QFrame or QWidget) and apply the background color to that container. This way the color spans the full width of the menu.
Here's a complete working example that demonstrates the approach:
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import (
QApplication,
QCheckBox,
QFrame,
QHBoxLayout,
QMainWindow,
QWidgetAction,
)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Custom Menu Item")
self.resize(640, 480)
# Standard actions
action1 = QAction("QAction Item 1", self)
action2 = QAction("QAction Item 2", self)
action2.setCheckable(True)
action3 = QAction("QAction Item 3", self)
# Custom styled menu item using QWidgetAction
widget_action = QWidgetAction(self)
# Create a container frame for the full-width background
container = QFrame()
container.setStyleSheet("background-color: tomato;")
layout = QHBoxLayout(container)
layout.setContentsMargins(4, 2, 4, 2)
check_box = QCheckBox("QWidgetAction Item")
check_box.setStyleSheet("background-color: tomato;")
layout.addWidget(check_box)
widget_action.setDefaultWidget(container)
# Build the menu
menu = self.menuBar().addMenu("Menu")
menu.addAction(action1)
menu.addAction(action2)
menu.addAction(widget_action)
menu.addAction(action3)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
With this setup, the QFrame container stretches to fill the full width of the menu, and the tomato background color covers the entire row.
Matching hover behavior
One thing you'll notice is that QWidgetAction items don't automatically get the same hover highlight as regular QAction items. The mouse hover styling is handled by the menu's style engine for standard actions, but custom widgets are on their own.
You can restore some of that behavior by using stylesheets on the container that respond to hover:
Packaging Python Applications with PyInstaller by Martin Fitzpatrick — This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.
container.setStyleSheet("""
QFrame {
background-color: tomato;
}
QFrame:hover {
background-color: #ff7f6e;
}
""")
This gives the item a slightly lighter shade when the mouse hovers over it, providing visual feedback similar to standard menu items.
A more polished example
Here's the full example with hover behavior included:
import sys
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import (
QApplication,
QCheckBox,
QFrame,
QHBoxLayout,
QMainWindow,
QWidgetAction,
)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Custom Menu Item")
self.resize(640, 480)
# Center the window on screen
available_geometry = self.screen().availableGeometry()
self.move(
int((available_geometry.width() - self.width()) / 2),
int((available_geometry.height() - self.height()) / 2),
)
# Standard actions
action1 = QAction("QAction Item 1", self)
action1.triggered.connect(lambda: print("Item 1 triggered"))
action2 = QAction("QAction Item 2", self)
action2.setCheckable(True)
action2.toggled.connect(lambda checked: print(f"Item 2 toggled: {checked}"))
action3 = QAction("QAction Item 3", self)
action3.triggered.connect(lambda: print("Item 3 triggered"))
# Custom styled menu item
widget_action = QWidgetAction(self)
container = QFrame()
container.setStyleSheet(
"""
QFrame {
background-color: tomato;
}
QFrame:hover {
background-color: #ff7f6e;
}
"""
)
layout = QHBoxLayout(container)
layout.setContentsMargins(4, 2, 4, 2)
self.custom_check_box = QCheckBox("Custom Styled Item")
self.custom_check_box.setStyleSheet(
"""
QCheckBox {
background-color: transparent;
padding: 2px;
}
"""
)
self.custom_check_box.toggled.connect(
lambda checked: print(f"Custom item toggled: {checked}")
)
layout.addWidget(self.custom_check_box)
widget_action.setDefaultWidget(container)
# Build the menu
menu = self.menuBar().addMenu("Menu")
menu.addAction(action1)
menu.addAction(action2)
menu.addAction(widget_action)
menu.addAction(action3)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Notice that the checkbox itself has background-color: transparent so it inherits the container's background color rather than painting its own solid block on top.
A note about menu closing behavior
One behavioral difference with QWidgetAction is that clicking the embedded widget does not automatically close the menu. With a standard QAction, clicking it triggers the action and closes the menu. With QWidgetAction, the menu stays open — which can actually be useful if you want users to toggle multiple checkboxes without having to reopen the menu each time.
If you do want the menu to close when the checkbox is clicked, you can connect the checkbox signal to the menu's close() method:
self.custom_check_box.toggled.connect(menu.close)
Add this after both the checkbox and the menu have been created. For more on how signals and connections work, see the Signals, Slots & Events tutorial.
Summary
Using QWidgetAction with a container QFrame gives you full control over the appearance of individual menu items. The container widget stretches to fill the menu width, and you can apply any stylesheet you like — background colors, fonts, borders, and more. Adding hover styles to the container keeps the visual feedback consistent with standard menu items, and connecting signals from your embedded widgets lets you respond to user interaction just like you would with regular actions.
If you're looking to further customize your application's appearance, you might also be interested in building custom widgets or arranging your UI with layouts.