How to Set Row Background Colors in a QTableView

Use Qt's BackgroundRole to color entire rows based on your data
Heads up! You've already completed this tutorial.

I have a QTableView table showing some data about connected devices. How can I highlight rows to give a visual indicator of the current status of the device?

When you're working with a QTableView and a custom model, it's common to want to highlight entire rows based on some condition in your data. For example, you might want to color a row blue when a device has a connected status, or red when something has gone wrong.

Understanding How data() Works

In Qt's Model/View architecture, the view calls your model's data() method for every cell in the table — and for each cell, it asks about multiple roles. One of those roles is Qt.BackgroundRole, which tells the view what background color to use for that cell.

The view asks for Qt.BackgroundRole on every single cell, not just one column. So if your data() method returns a color for Qt.BackgroundRole based on the row data (ignoring the column), the color will be applied to every cell in that row.

Let's build a working example.

A Complete Working Example

Here's a full example you can run directly. It creates a QTableView with colored rows based on the PRESENT_STATUS field in each row of data:

python
import sys
from typing import Union

from PyQt6.QtCore import QAbstractTableModel, QModelIndex, Qt
from PyQt6.QtGui import QColor
from PyQt6.QtWidgets import QApplication, QMainWindow, QTableView


class TableModel(QAbstractTableModel):

    def __init__(self, data: Union[list, None] = None):
        super().__init__()
        self._data = data or []
        self._hdr = self._gen_hdr_data() if data else []
        self._base_color = {
            "NewConnection": QColor("blue"),
            "Registered": QColor("green"),
        }

    def _gen_hdr_data(self):
        """Build a sorted list of all unique keys across all row dicts."""
        all_keys = set()
        for d in self._data:
            all_keys.update(d.keys())
        return sorted(all_keys)

    def rowCount(self, parent=QModelIndex()):
        return len(self._data)

    def columnCount(self, parent=QModelIndex()):
        return len(self._hdr)

    def headerData(self, section, orientation, role):
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            return self._hdr[section]

    def data(self, index: QModelIndex, role: int):
        if not index.isValid():
            return None

        row_dict = self._data[index.row()]
        state = row_dict.get("PRESENT_STATUS", "")

        if role == Qt.DisplayRole:
            col_key = self._hdr[index.column()]
            value = row_dict.get(col_key, "")
            return str(value) if value else ""

        if role == Qt.BackgroundRole:
            color = self._base_color.get(state)
            if color:
                return color

        return None


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Row Background Colors in QTableView")

        data = [
            {"IP": "192.168.1.10", "PRESENT_STATUS": "NewConnection"},
            {"IP": "192.168.1.108", "FORMER_STATUS": "NewConnection",
             "PRESENT_STATUS": "Registered"},
            {"IP": "192.168.1.50", "PRESENT_STATUS": "Unknown"},
        ]

        self.table = QTableView()
        model = TableModel(data)
        self.table.setModel(model)
        self.setCentralWidget(self.table)


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

The method that Qt calls on the model is called data, so in the example above, the list is stored as self._data (with a leading underscore) to avoid this.

Run this and you'll see three rows. The first row ("NewConnection") has a blue background, the second row ("Registered") has a green background, and the third row ("Unknown") has no special coloring because it isn't in the _base_color dictionary.

QTableView with colored rows based on status values

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 ]]

How Colors are Set on Rows

To understand how the color is being set to the entire row, take a look at the Qt.BackgroundRole section of data():

python
if role == Qt.BackgroundRole:
    color = self._base_color.get(state)
    if color:
        return color

Notice that index.column() isn't used here at all. The color decision is based entirely on the row's PRESENT_STATUS value. Since the view calls data() for every cell in the row — column 0, column 1, column 2, etc. — and each call gets the same color back, the entire row ends up painted.

If you only wanted to color a specific column (say, just the status column), you would add a column check:

python
if role == Qt.BackgroundRole:
    # Only color the PRESENT_STATUS column
    if self._hdr[index.column()] == "PRESENT_STATUS":
        color = self._base_color.get(state)
        if color:
            return color

Making the Text Readable

One thing you'll notice with a dark background color like blue is that the default black text becomes hard to read. You can fix this by also handling Qt.ForegroundRole and returning a light text color when the background is dark:

python
def data(self, index: QModelIndex, role: int):
    if not index.isValid():
        return None

    row_dict = self._data[index.row()]
    state = row_dict.get("PRESENT_STATUS", "")

    if role == Qt.DisplayRole:
        col_key = self._hdr[index.column()]
        value = row_dict.get(col_key, "")
        return str(value) if value else ""

    if role == Qt.BackgroundRole:
        color = self._base_color.get(state)
        if color:
            return color

    if role == Qt.ForegroundRole:
        # If this row has a background color, use white text.
        if state in self._base_color:
            return QColor("white")

    return None

Now blue and green rows will have white text, making everything easy to read.

Updating Colors Dynamically

If your data changes at runtime — for example, a device's status changes from "NewConnection" to "Registered" — you need to tell the view that something has changed so it repaints. You do this by emitting the dataChanged signal:

python
def update_status(self, row, new_status):
    self._data[row]["PRESENT_STATUS"] = new_status
    # Emit dataChanged for the entire row.
    top_left = self.index(row, 0)
    bottom_right = self.index(row, self.columnCount() - 1)
    self.dataChanged.emit(top_left, bottom_right)

This tells the view to re-query data() for every cell in that row, which picks up both the new display text and the new background color. For a deeper look at how signals work to keep your model and view in sync, see Signals, Slots & Events.

Summary

Once you understand how the model's data() method works, coloring entire rows in a QTableView is relatively straightforward. The view asks for each role on every cell, so returning a color from Qt.BackgroundRole based on row-level data — without filtering by column — naturally paints the whole row. Pair that with Qt.ForegroundRole for readable text, and you've got a clean, data-driven way to highlight rows in your table.

To learn more about using QTableView with custom models and data from numpy or pandas, see the QTableView with numpy and pandas tutorial. If you want to add sorting and filtering to your table, take a look at Sorting and Filtering Tables.

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick — (PyQt6 Edition) The hands-on guide to making apps with Python — Save time and build better with this book. Over 15K copies sold.

Get the book

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak
Martin Fitzpatrick

How to Set Row Background Colors in a QTableView 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.