Skip to content

QML Cheat Sheet

Overview

QML (Qt Modeling Language) is a declarative language for designing user interfaces in Qt applications. It combines a JSON-like syntax for describing UI structure with inline JavaScript for logic, and connects to C++ backends for performance-critical operations and system access. QML is the foundation of Qt Quick, Qt’s framework for building modern, fluid UIs with smooth animations, touch interaction, and GPU-accelerated rendering via OpenGL, Vulkan, or Metal.

QML excels at creating animated, responsive interfaces for desktop, mobile, and embedded applications. It uses a property-binding system where UI elements automatically update when underlying data changes, eliminating manual UI refresh code. The language supports component composition, custom visual effects through shaders, advanced input handling, and a rich set of built-in types for layouts, animations, and visual effects. Qt Creator provides a visual design tool for QML with live preview and debugging.

Installation

# Install Qt (includes QML/Qt Quick)
# Option 1: Online installer from qt.io
# https://www.qt.io/download-qt-installer

# Option 2: Package managers
# macOS
brew install qt

# Ubuntu/Debian
sudo apt install qt6-base-dev qt6-declarative-dev qml6-module-qtquick \
  qml6-module-qtquick-controls qml6-module-qtquick-layouts \
  qt6-tools-dev qt-creator

# Arch Linux
sudo pacman -S qt6-base qt6-declarative qt6-tools qt-creator

# Fedora
sudo dnf install qt6-qtbase-devel qt6-qtdeclarative-devel qt-creator

# Verify
qmake --version
# Or with CMake
cmake --version

# Create new Qt Quick project
# Qt Creator: File > New Project > Qt Quick Application

Basic QML Structure

// main.qml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts

ApplicationWindow {
    id: root
    width: 800
    height: 600
    visible: true
    title: "My QML App"

    // Properties
    property int counter: 0
    property string userName: "World"
    property bool isDarkMode: false

    // Main content
    ColumnLayout {
        anchors.centerIn: parent
        spacing: 16

        Text {
            text: "Hello, " + userName + "!"
            font.pixelSize: 28
            font.bold: true
            color: isDarkMode ? "white" : "black"
            Layout.alignment: Qt.AlignHCenter
        }

        TextField {
            id: nameInput
            placeholderText: "Enter your name"
            Layout.preferredWidth: 250
            Layout.alignment: Qt.AlignHCenter
            onTextChanged: userName = text || "World"
        }

        Button {
            text: "Count: " + counter
            Layout.alignment: Qt.AlignHCenter
            onClicked: counter++
        }
    }
}

Common Types

TypeDescription
RectangleColored rectangle (basic shape)
TextText display
ImageImage display
MouseAreaMouse interaction area
TextInputSingle-line text input
TextEditMulti-line text editor
FlickableScrollable/flick area
ListViewScrollable list
GridViewGrid of items
RepeaterRepeat items from model
LoaderDynamic component loading
Canvas2D drawing surface
TimerPeriodic/single-shot timer

Qt Quick Controls

ControlDescription
ApplicationWindowMain window
ButtonClickable button
TextFieldText input
TextAreaMulti-line input
CheckBoxBoolean toggle
RadioButtonSingle selection
SwitchOn/off toggle
SliderValue range
SpinBoxNumeric stepper
ComboBoxDropdown selection
ProgressBarProgress display
BusyIndicatorLoading indicator
DialogModal dialog
MenuBar / MenuApplication menus
TabBar / TabButtonTabbed interface
ToolBar / ToolButtonToolbar
DrawerSlide-out panel
StackViewPage navigation
SwipeViewSwipeable pages

Layouts and Positioning

// Anchors (relative positioning)
Rectangle {
    id: parent
    width: 400; height: 300
    
    Rectangle {
        id: header
        anchors.top: parent.top
        anchors.left: parent.left
        anchors.right: parent.right
        height: 50
        color: "steelblue"
    }
    
    Rectangle {
        anchors.top: header.bottom
        anchors.bottom: parent.bottom
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.margins: 10
        color: "lightgray"
    }
}

// ColumnLayout
ColumnLayout {
    spacing: 8
    
    Label { text: "Name:"; Layout.fillWidth: true }
    TextField { Layout.fillWidth: true }
    Label { text: "Email:"; Layout.fillWidth: true }
    TextField { Layout.fillWidth: true }
    
    RowLayout {
        Layout.fillWidth: true
        Item { Layout.fillWidth: true }  // Spacer
        Button { text: "Cancel" }
        Button { text: "Submit" }
    }
}

// GridLayout
GridLayout {
    columns: 3
    rowSpacing: 8
    columnSpacing: 8
    
    Label { text: "Name:" }
    TextField { Layout.columnSpan: 2; Layout.fillWidth: true }
    
    Label { text: "City:" }
    TextField { Layout.fillWidth: true }
    ComboBox { model: ["US", "UK", "DE"] }
}

// Flow (wrap layout)
Flow {
    width: 300
    spacing: 8
    
    Repeater {
        model: ["Tag1", "Tag2", "Tag3", "Tag4", "Tag5"]
        delegate: Rectangle {
            width: label.width + 16
            height: 30
            radius: 15
            color: "steelblue"
            Text { id: label; text: modelData; anchors.centerIn: parent; color: "white" }
        }
    }
}

Property Binding and Signals

Rectangle {
    id: myRect
    width: 200
    height: width * 0.75  // Binding: auto-updates when width changes
    color: mouseArea.containsMouse ? "lightblue" : "lightgray"
    
    // Custom properties
    property real progress: 0.5
    property alias labelText: label.text
    
    // Signal declarations
    signal clicked(int x, int y)
    signal valueChanged(real newValue)
    
    Text {
        id: label
        text: "Progress: " + (parent.progress * 100).toFixed(0) + "%"
        anchors.centerIn: parent
    }
    
    MouseArea {
        id: mouseArea
        anchors.fill: parent
        hoverEnabled: true
        onClicked: (mouse) => {
            parent.clicked(mouse.x, mouse.y)
        }
    }
    
    // Signal handler
    onProgressChanged: {
        console.log("Progress:", progress)
        if (progress >= 1.0) {
            valueChanged(progress)
        }
    }
}

// Connect signals
Connections {
    target: myRect
    function onClicked(x, y) {
        console.log("Clicked at:", x, y)
    }
}

Lists and Models

// ListView with model
ListView {
    id: listView
    width: 300
    height: 400
    clip: true
    spacing: 4
    
    model: ListModel {
        ListElement { name: "Alice"; role: "Developer" }
        ListElement { name: "Bob"; role: "Designer" }
        ListElement { name: "Carol"; role: "Manager" }
    }
    
    delegate: Rectangle {
        width: listView.width
        height: 60
        color: ListView.isCurrentItem ? "#e3f2fd" : "white"
        radius: 4
        
        RowLayout {
            anchors.fill: parent
            anchors.margins: 8
            
            ColumnLayout {
                Text { text: model.name; font.bold: true }
                Text { text: model.role; color: "gray" }
            }
            Item { Layout.fillWidth: true }
            Button {
                text: "Edit"
                onClicked: console.log("Edit", model.name)
            }
        }
        
        MouseArea {
            anchors.fill: parent
            onClicked: listView.currentIndex = index
        }
    }
    
    // Add/remove items
    function addItem(name, role) {
        model.append({ name: name, role: role })
    }
    
    function removeItem(index) {
        model.remove(index)
    }
}

Animations

Rectangle {
    id: animRect
    width: 100; height: 100
    color: "steelblue"
    radius: 10
    
    // Property animation
    PropertyAnimation on x {
        from: 0; to: 300
        duration: 2000
        easing.type: Easing.InOutQuad
        loops: Animation.Infinite
    }
    
    // State-based animation
    states: [
        State {
            name: "expanded"
            PropertyChanges { target: animRect; width: 200; height: 200; color: "coral" }
        }
    ]
    
    transitions: [
        Transition {
            from: ""; to: "expanded"
            reversible: true
            ParallelAnimation {
                NumberAnimation { properties: "width,height"; duration: 300; easing.type: Easing.OutBounce }
                ColorAnimation { duration: 300 }
            }
        }
    ]
    
    MouseArea {
        anchors.fill: parent
        onClicked: animRect.state = animRect.state === "expanded" ? "" : "expanded"
    }
}

// Behavior animation (animate any property change)
Rectangle {
    width: 100; height: 100
    x: mouseArea.mouseX
    y: mouseArea.mouseY
    
    Behavior on x { NumberAnimation { duration: 200 } }
    Behavior on y { NumberAnimation { duration: 200 } }
    Behavior on color { ColorAnimation { duration: 500 } }
}

// Sequential animation
SequentialAnimation {
    id: seqAnim
    NumberAnimation { target: rect; property: "x"; to: 300; duration: 500 }
    PauseAnimation { duration: 200 }
    NumberAnimation { target: rect; property: "y"; to: 300; duration: 500 }
    NumberAnimation { target: rect; property: "opacity"; to: 0; duration: 300 }
}
// seqAnim.start()

C++ Integration

// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "backend.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    
    QQmlApplicationEngine engine;
    
    // Register C++ type for QML
    qmlRegisterType<Backend>("com.example", 1, 0, "Backend");
    
    // Expose C++ object as context property
    Backend backend;
    engine.rootContext()->setContextProperty("appBackend", &backend);
    
    engine.loadFromModule("MyApp", "Main");
    return app.exec();
}

// backend.h
class Backend : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString status READ status WRITE setStatus NOTIFY statusChanged)
    Q_PROPERTY(int count READ count NOTIFY countChanged)

public:
    explicit Backend(QObject *parent = nullptr);
    
    QString status() const { return m_status; }
    void setStatus(const QString &status);
    int count() const { return m_count; }
    
    Q_INVOKABLE void processData(const QString &input);
    Q_INVOKABLE QVariantList getItems();

signals:
    void statusChanged();
    void countChanged();
    void dataReady(const QVariantMap &result);

private:
    QString m_status;
    int m_count = 0;
};
// Using C++ backend in QML
import com.example 1.0

ApplicationWindow {
    Backend {
        id: backend
        onDataReady: (result) => {
            console.log("Data:", JSON.stringify(result))
        }
    }
    
    Text { text: backend.status }
    
    Button {
        text: "Process"
        onClicked: backend.processData("input data")
    }
}

Configuration

# CMakeLists.txt
cmake_minimum_required(VERSION 3.21)
project(myapp VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)

find_package(Qt6 REQUIRED COMPONENTS Quick QuickControls2)

qt_standard_project_setup(REQUIRES 6.5)

qt_add_executable(myapp main.cpp backend.cpp backend.h)

qt_add_qml_module(myapp
    URI MyApp
    VERSION 1.0
    QML_FILES
        Main.qml
        components/Header.qml
        components/Sidebar.qml
    RESOURCES
        images/logo.png
        fonts/custom.ttf
)

target_link_libraries(myapp PRIVATE Qt6::Quick Qt6::QuickControls2)

Advanced Usage

// Custom component
// components/Card.qml
import QtQuick
import QtQuick.Controls

Rectangle {
    id: card
    property alias title: titleText.text
    property alias content: contentText.text
    default property alias children: contentArea.children
    
    width: 300
    height: column.height + 32
    radius: 8
    color: "white"
    border.color: "#e0e0e0"
    
    layer.enabled: true
    layer.effect: DropShadow { radius: 8; color: "#20000000" }
    
    Column {
        id: column
        anchors { left: parent.left; right: parent.right; top: parent.top; margins: 16 }
        spacing: 8
        
        Text { id: titleText; font { pixelSize: 18; bold: true } }
        Text { id: contentText; wrapMode: Text.WordWrap; width: parent.width }
        Item { id: contentArea; width: parent.width; height: childrenRect.height }
    }
}

// Shader effect
ShaderEffect {
    width: 300; height: 300
    property real time: 0
    
    NumberAnimation on time {
        from: 0; to: Math.PI * 2
        duration: 3000; loops: Animation.Infinite
    }
    
    fragmentShader: "
        varying highp vec2 qt_TexCoord0;
        uniform highp float time;
        void main() {
            highp float r = 0.5 + 0.5 * sin(qt_TexCoord0.x * 10.0 + time);
            highp float g = 0.5 + 0.5 * cos(qt_TexCoord0.y * 10.0 + time);
            gl_FragColor = vec4(r, g, 0.5, 1.0);
        }"
}

Troubleshooting

IssueSolution
”Module not found”Check import statement; verify QML modules installed
Property binding loopBreak circular dependencies; use Qt.binding() carefully
Component not loadingCheck file name matches component name; verify QML path
C++ type not visibleEnsure qmlRegisterType or QML_ELEMENT macro used
Black screen / no renderingCheck visible: true on Window; verify scene graph created
Memory leakUse Component.onDestruction; properly disconnect signals
Poor performanceAvoid complex bindings; use Loader for lazy loading; profile with Qt Creator
Touch not workingEnsure MouseArea covers the area; check propagateComposedEvents
Font not renderingBundle fonts; use FontLoader for custom fonts
Deploy missing QML modulesUse windeployqt / macdeployqt; check qmlimportscanner output