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
| Type | Description |
|---|
Rectangle | Colored rectangle (basic shape) |
Text | Text display |
Image | Image display |
MouseArea | Mouse interaction area |
TextInput | Single-line text input |
TextEdit | Multi-line text editor |
Flickable | Scrollable/flick area |
ListView | Scrollable list |
GridView | Grid of items |
Repeater | Repeat items from model |
Loader | Dynamic component loading |
Canvas | 2D drawing surface |
Timer | Periodic/single-shot timer |
Qt Quick Controls
| Control | Description |
|---|
ApplicationWindow | Main window |
Button | Clickable button |
TextField | Text input |
TextArea | Multi-line input |
CheckBox | Boolean toggle |
RadioButton | Single selection |
Switch | On/off toggle |
Slider | Value range |
SpinBox | Numeric stepper |
ComboBox | Dropdown selection |
ProgressBar | Progress display |
BusyIndicator | Loading indicator |
Dialog | Modal dialog |
MenuBar / Menu | Application menus |
TabBar / TabButton | Tabbed interface |
ToolBar / ToolButton | Toolbar |
Drawer | Slide-out panel |
StackView | Page navigation |
SwipeView | Swipeable 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
| Issue | Solution |
|---|
| ”Module not found” | Check import statement; verify QML modules installed |
| Property binding loop | Break circular dependencies; use Qt.binding() carefully |
| Component not loading | Check file name matches component name; verify QML path |
| C++ type not visible | Ensure qmlRegisterType or QML_ELEMENT macro used |
| Black screen / no rendering | Check visible: true on Window; verify scene graph created |
| Memory leak | Use Component.onDestruction; properly disconnect signals |
| Poor performance | Avoid complex bindings; use Loader for lazy loading; profile with Qt Creator |
| Touch not working | Ensure MouseArea covers the area; check propagateComposedEvents |
| Font not rendering | Bundle fonts; use FontLoader for custom fonts |
| Deploy missing QML modules | Use windeployqt / macdeployqt; check qmlimportscanner output |