PY

pyside6-qml-views

Guide to building QML-based PySide6 UIs. Focuses on component-based design and declarative styling.

Install

mkdir -p .claude/skills/pyside6-qml-views && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/10037" && unzip -o skill.zip -d .claude/skills/pyside6-qml-views && rm skill.zip

Installs to .claude/skills/pyside6-qml-views

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Use this skill when creating QML view files, designing QML component hierarchies, building layouts, styling QML controls, creating reusable QML components, implementing QML navigation / page switching, or working with QML resources. Covers QML file structure, component patterns, Material/Controls styling, resource management, and common QML idioms for desktop applications.
375 chars✓ has a “when” triggerlonger than Claude Code's old 250-char listing cap (fine on current versions)
Intermediate

Key capabilities

  • Create QML components
  • Design component hierarchies
  • Implement page navigation
  • Style with Material/Controls
  • Bind Python bridge properties

How it works

It uses declarative QML files that bind to Python bridge properties, keeping business logic separate.

Inputs & outputs

You give it
UI design
You get back
QML view files

When to use pyside6-qml-views

  • Creating reusable QML components
  • Setting up QML file hierarchy
  • Styling desktop apps with QML

About this skill

PySide6 QML Views

All UI in this architecture is defined declaratively in .qml files. QML views bind to Python bridge properties and call bridge slots — they contain no business logic.

QML File Organization

resources/
├── qml/
│   ├── main.qml                  # Root window / StackLayout host
│   ├── components/
│   │   ├── ActionButton.qml      # Reusable styled button
│   │   ├── StatusBadge.qml       # Status indicator
│   │   ├── SearchBar.qml         # Search input with debounce
│   │   ├── LoadingOverlay.qml    # Busy spinner overlay
│   │   └── ErrorBanner.qml       # Error message bar
│   ├── pages/
│   │   ├── JobListPage.qml       # Job listing with cards
│   │   ├── JobDetailPage.qml     # Single job detail view
│   │   ├── SettingsPage.qml      # App settings form
│   │   └── DashboardPage.qml     # Overview / landing page
│   ├── dialogs/
│   │   ├── CreateJobDialog.qml   # Modal dialog for new job
│   │   └── ConfirmDialog.qml     # Generic confirmation popup
│   └── styles/
│       ├── Theme.qml             # Colour palette, spacing, fonts
│       └── qmldir                # Module metadata for imports
├── icons/
│   ├── *.svg                     # Vector icons
│   └── *.png                     # Raster icons
└── qml.qrc                       # Qt resource file (optional)

Root Window (main.qml)

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

ApplicationWindow {
    id: root
    visible: true
    width: 1280
    height: 720
    title: "My Application"

    // Page navigation
    StackLayout {
        id: pageStack
        anchors.fill: parent
        currentIndex: 0

        JobListPage {}
        JobDetailPage {}
        SettingsPage {}
    }

    // Global toolbar
    header: ToolBar {
        RowLayout {
            anchors.fill: parent

            Label {
                text: "My App"
                font.bold: true
                Layout.leftMargin: 12
            }

            Item { Layout.fillWidth: true }

            ToolButton {
                text: "Jobs"
                onClicked: pageStack.currentIndex = 0
            }
            ToolButton {
                text: "Settings"
                onClicked: pageStack.currentIndex = 2
            }
        }
    }

    // Global error banner
    ErrorBanner {
        id: errorBanner
        anchors { top: parent.top; left: parent.left; right: parent.right }
        visible: jobBridge.errorMessage !== ""
        message: jobBridge.errorMessage
    }

    // Loading overlay
    LoadingOverlay {
        anchors.fill: parent
        visible: jobBridge.isBusy
    }
}

Page Pattern

Every page is a self-contained QML file that binds to bridge properties:

// pages/JobListPage.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Page {
    id: jobListPage

    header: ToolBar {
        RowLayout {
            anchors.fill: parent
            SearchBar {
                id: searchBar
                Layout.fillWidth: true
                onSearchTriggered: jobBridge.searchJobs(query)
            }
            ActionButton {
                text: "New Job"
                icon.name: "add"
                onClicked: createJobDialog.open()
            }
        }
    }

    ListView {
        id: jobsListView
        anchors.fill: parent
        model: jobListModel
        spacing: 4
        clip: true

        delegate: ItemDelegate {
            width: jobsListView.width
            height: 64

            contentItem: RowLayout {
                spacing: 12
                Label {
                    text: model.jobNumber
                    font.bold: true
                    Layout.preferredWidth: 100
                }
                Label {
                    text: model.jobName
                    Layout.fillWidth: true
                    elide: Text.ElideRight
                }
                StatusBadge {
                    status: model.status
                }
            }

            onClicked: {
                jobBridge.activateJob(model.jobNumber)
                pageStack.currentIndex = 1  // navigate to detail
            }
        }

        // Empty state
        Label {
            anchors.centerIn: parent
            visible: jobsListView.count === 0
            text: "No jobs found"
            opacity: 0.5
        }
    }

    CreateJobDialog {
        id: createJobDialog
    }
}

Reusable Component Pattern

Component File Structure

// components/ActionButton.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

Button {
    id: control

    // Custom properties
    property color accentColor: "#1976D2"
    property bool loading: false

    enabled: !loading
    opacity: enabled ? 1.0 : 0.5

    contentItem: Row {
        spacing: 8
        BusyIndicator {
            running: control.loading
            visible: control.loading
            width: 16; height: 16
        }
        Label {
            text: control.text
            color: "white"
            verticalAlignment: Text.AlignVCenter
        }
    }

    background: Rectangle {
        radius: 4
        color: control.down ? Qt.darker(accentColor, 1.2)
             : control.hovered ? Qt.lighter(accentColor, 1.1)
             : accentColor
    }
}

Component with Custom Signals

// components/SearchBar.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

TextField {
    id: searchField

    signal searchTriggered(string query)

    placeholderText: "Search..."
    selectByMouse: true

    // Debounced search
    Timer {
        id: debounceTimer
        interval: 300
        onTriggered: searchField.searchTriggered(searchField.text)
    }

    onTextChanged: debounceTimer.restart()
    onAccepted: {
        debounceTimer.stop()
        searchTriggered(text)
    }
}

Dialog Pattern

// dialogs/CreateJobDialog.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15

Dialog {
    id: dialog
    title: "Create New Job"
    modal: true
    anchors.centerIn: Overlay.overlay
    width: 400
    standardButtons: Dialog.Ok | Dialog.Cancel

    onAccepted: {
        if (jobNumberInput.text.trim() !== "") {
            jobBridge.createJob(jobNumberInput.text.trim())
        }
    }
    onRejected: dialog.close()

    // Reset on open
    onOpened: {
        jobNumberInput.text = ""
        jobNumberInput.forceActiveFocus()
    }

    ColumnLayout {
        anchors.fill: parent
        spacing: 12

        Label { text: "Job Number" }
        TextField {
            id: jobNumberInput
            Layout.fillWidth: true
            placeholderText: "e.g. 1234567"
            validator: RegularExpressionValidator {
                regularExpression: /^\d{5,8}[A-Z]?$/
            }
        }

        Label {
            text: "Enter a valid job number (5-8 digits, optional letter suffix)"
            font.pixelSize: 11
            opacity: 0.6
        }
    }
}

Theme / Styling

Theme Singleton

// styles/Theme.qml
pragma Singleton
import QtQuick 2.15

QtObject {
    // Colours
    readonly property color primary: "#1976D2"
    readonly property color primaryDark: "#1565C0"
    readonly property color accent: "#FF9800"
    readonly property color background: "#FAFAFA"
    readonly property color surface: "#FFFFFF"
    readonly property color error: "#D32F2F"
    readonly property color textPrimary: "#212121"
    readonly property color textSecondary: "#757575"

    // Spacing
    readonly property int spacingSmall: 4
    readonly property int spacingMedium: 8
    readonly property int spacingLarge: 16
    readonly property int spacingXLarge: 24

    // Typography
    readonly property int fontSizeSmall: 12
    readonly property int fontSizeMedium: 14
    readonly property int fontSizeLarge: 18
    readonly property int fontSizeTitle: 24

    // Elevation / Radii
    readonly property int borderRadius: 4
    readonly property int cardRadius: 8
}

qmldir (module registration)

// styles/qmldir
module Styles
singleton Theme 1.0 Theme.qml

Usage

import "styles" as Styles

Rectangle {
    color: Styles.Theme.surface
    radius: Styles.Theme.cardRadius

    Label {
        color: Styles.Theme.textPrimary
        font.pixelSize: Styles.Theme.fontSizeMedium
    }
}

Loading States

// components/LoadingOverlay.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    id: overlay
    color: "#80000000"  // semi-transparent black
    visible: false
    z: 999

    BusyIndicator {
        anchors.centerIn: parent
        running: overlay.visible
        width: 48; height: 48
    }

    MouseArea {
        anchors.fill: parent
        // Block clicks through overlay
    }
}

Status Indicator

// components/StatusBadge.qml
import QtQuick 2.15
import QtQuick.Controls 2.15

Rectangle {
    id: badge
    property string status: ""

    width: statusLabel.implicitWidth + 16
    height: 24
    radius: 12
    color: {
        switch (status.toLowerCase()) {
            case "active":   return "#4CAF50";
            case "complete": return "#2196F3";
            case "on_hold":  return "#FF9800";
            case "archived": return "#9E9E9E";
            default:         return "#BDBDBD";
        }
    }

    Label {
        id: statusLabel
        anchors.centerIn: parent
        text: badge.status
        color: "white"
        font.pixelSize: 11
        font.bold: true
    }
}

QML Best Practices

RuleRationale
Keep components under 150 linesMaintainability; extract sub-components
One root item per fileQML convention
Use id only when referencedAvoid unnecessary identity
Prefer property bindings over imperative JSDeclarative updates, fewer bugs
Use Loader for heavy / conditional contentLazy instantiation saves memory
Never embed SQL, HTTP, or file I/O

Content truncated.

When not to use it

  • Embedding SQL or I/O in QML JS

Prerequisites

PySide6

Limitations

  • Never embed SQL, HTTP, or file I/O in QML JS

How it compares

It uses a declarative UI approach with a clear separation from Python business logic.

Compared to similar skills

pyside6-qml-views side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
pyside6-qml-views (this skill)05moNo flagsIntermediate
textual1439moReviewIntermediate
qt-qml05moNo flagsIntermediate
anywidget-marimo05moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry