
QML Best Practices
- Nathan PRIOR
- Best practices
- August 26, 2025
In short
QML best practices to write clean, maintainable, and high-performance code with Qt Quick

QML best practices to write clean, maintainable, and high-performance code with Qt Quick
In this article, I share with you some QML best practices that will help you avoid classic pitfalls and build interfaces that are clear, responsive, and easy to evolve.
How many times have you heard (or thought): “This code is a mess, we should rewrite everything!”
Whether you code in QML, Python, JavaScript or anything else, this phrase is a symptom: somewhere, best practices haven’t been applied. Or worse, they’ve been ignored in favor of speed.
In this article, I share with you what I’ve learned (sometimes the hard way) about best practices, conventions and pitfalls to avoid.
For me, it’s a habit or rule that brings tangible benefit:
Example: Clear variable naming, separation of responsibilities, order of property declarations… Details? Yes. But it’s like oil in an engine: without it, everything creaks.
⚠️ Warning: Some good practices require initial effort. A junior might find them “useless”, a senior will see it as time saved in the long run. This is normal: their value reveals itself with experience.
A convention is an arbitrary but shared rule. It doesn’t matter whether you use tabs or spaces, the important thing is that the whole team does the same.
Why?
My advice: Document your conventions (a CONTRIBUTING.md or wiki is enough). It avoids endless debates and Merge/Pull Requests blocked over details.
Bad practices are like technical debt: we contract them without thinking about it, and one day, they blow up in our face.
Some telltale signs:
My advice: Take the time to do things right, even if it’s longer at first. Your future self will thank you.
And now, let’s get practical! We’re now going to see how to apply these principles concretely in QML (organization, readability, maintainability…). But first, a question: What’s the worst bad practice you’ve seen (or committed)? 😉 Feel free to respond in the comments of this article.
My nightmare? A project where all files were piled up at the root – no folders, no structure, just 200 or 300 C++ files lost in the same folder. Result: a daily treasure hunt to find a class. The worst part? The team seniors found this normal after 15 years of development, and the junior interns believed hard as iron that this was the standard way to organize a project. In short, developer hell.
QML is a declarative and flexible language, ideal for creating fluid and dynamic interfaces. But this freedom comes with a price: without rigor, you quickly end up with spaghetti code, where the visual hierarchy no longer reflects the logical structure, and where bindings (property: value) become sneaky traps.
Why is QML different? QML has a particularity: it allows the possibility to merge interface and logic (thanks to integrated JavaScript). Without vigilance, this flexibility turns against you:
width: parent.width * someComplexFormula) make application behavior unpredictable and hard to debug.Button that calculates a price or validates a form is tempting… but it’s also opening the door to bugs and technical debt.Consequence? Code where everything depends on everything, where a minor modification breaks three screens, and where nobody dares touch anything anymore.
Fortunately, a few simple rules are enough to avoid chaos. Let’s review them, section by section.
Where to start? With organization, of course! A clear project structure is like a good foundation: if it’s solid, everything else follows. Otherwise, prepare for sleepless nights of refactoring.
In a professional project mixing C++ and QML, you often end up with a ton of files: .h, .cpp, .qml, .js, .png, .svg, .ttf, CMakeLists.txt, etc. The challenge? Finding a balance between rigor and flexibility, as there’s no universal solution. Here are two examples of structures I’ve experimented with, along with their strengths and limitations.
project_name/
├── include/ # C++ headers
│ └── project_name/
│ └── cpp_folder/
│ └── MyClass.h
├── resources/ # Static resources (images, fonts, etc.)
│ ├── images/
│ └── fonts/
├── src/
│ ├── main.cpp # C++ entry point
│ ├── cpp_folder/ # C++ implementations
│ │ └── MyClass.cpp
│ └── qml/ # QML code
│ └── qml_folder/
│ └── Component.qml
├── test/ # Unit tests
│ └── cpp_folder/
│ └── MyClassTest.h
├── .clang-format # Automatic formatting
├── .clang-tidy # Static analysis
├── .gitignore
└── CMakeLists.txt # Build configuration
Why does this work?
project_name/
├── app/ # Main application (same structure as above)
|
├── core_library/ # C++ library (business + technical, under Git repo)
|
├── ui_library/ # QML library (reusable graphical components, under Git repo)
│ ├── token/ # Design tokens (colors, fonts, sizes)
│ ├── atom/ # Atomic components (buttons, text fields)
│ ├── molecule/ # Composite components (section + title + button)
│ ├── organism/ # Complex components (headers, footers)
│ └── resources/ # UI-specific resources
|
├── modules/ # Independent modules (each module = a Git sub-project)
│ ├── module1/
│ └── module2/
|
├── .clang-format
├── .clang-tidy
├── .gitignore
├── .gitmodules # For Git submodules
└── CMakeLists.txt
Why this structure?
core_library and ui_library libraries are autonomous and can be shared between multiple projects.To remember:
README.md, Confluence, Notion, etc.).misc/ or temp/ – they quickly become digital garbage bins.I present here two approaches to organize your QML files. Feel free to mix them according to your needs!
Ideal if your application is business-oriented (ex.: a deals app).
project_name/
└── qml/
├── header/ # Application header
│ ├── Header.qml
│ └── SettingsPanel.qml
├── dashboard/ # Dashboard
│ ├── Dashboard.qml
│ └── MyGoodDeals.qml
├── menu/ # Navigation menu
│ ├── Menu.qml
│ └── MenuItem.qml
├── feature/ # Business features
│ ├── profil/
│ │ ├── ProfilPage.qml
│ │ └── ProfilResumePanel.qml
│ └── deal/
│ ├── DealPage.qml
│ └── LastDealListPanel.qml
└── Main.qml # QML entry point
Why?
Ideal if you use a design system or component library.
project_name/
└── qml/
├── token/ # Application style and base variables
│ ├── Fonts.qml
│ ├── Icons.qml
├── atom/ # Simple components (atomic)
│ ├── Button.qml
│ ├── Divider.qml
│ ├── SpecificButton.qml
├── molecule/ # Complex components (composed of atoms)
│ ├── ProfilPanel.qml
│ └── DealCard.qml
├── organism/ # Entire panels (composed of molecules / atoms)
│ ├── Header.qml
│ └── Menu.qml
└── page/ # Complete pages
└── Dashboard.qml
Why?
My advice:
Readability is like spelling: everyone knows it’s important, but few people make it a priority. Yet readable code is maintainable, collaborative and less stressful code.
I won’t rehash the good practices you already know by heart:
userProfileAvatar > img1.)No, let’s focus now on what is specific to QML.
In QML, declaration order matters. Not for the compiler, but for your colleagues (and your future self). Here’s the order I recommend, tested and approved on real projects:
id (always first)property declarations)anchors, width, height)states)transitions)copy-paste without moderation
import QtQuick // Qt imports
import my.project // Internal plugin or module
import "my/folder" // Relative import
Item {
id: root // Always first!
//========================================================================
// Properties
//========================================================================
property bool isActive // false by default, so no re-assignment
property string userName // "" by default, so no re-assignment
//========================================================================
// Inherited properties
//========================================================================
anchors.fill: parent
width: 200
height: childrenRect.height
//========================================================================
// Signals
//========================================================================
signal userUpdated(string newName)
//========================================================================
// Functions
//========================================================================
function calculateWidth() {
return root.width * 0.8
}
//========================================================================
// Private elements
//========================================================================
QtObject {
id: internal
property string userPwd
}
//========================================================================
// Child objects
//========================================================================
Rectangle {
color: "lightgray"
anchors.fill: parent
}
//========================================================================
// States
//========================================================================
states: [
State {
name: "active"
when: root.isActive
PropertyChanges { target: root; color: "blue" }
}
]
//========================================================================
// Transitions
//========================================================================
transitions: [
Transition {
from: "*"
to: "active"
ColorAnimation { duration: 200 }
}
]
}
Why this order?
propertiesLittle bonus:
// ====) to delimit sections.property bool isActive: false).Maintainability is the art of not shooting yourself in the foot in 6 months. Maintainable code is code where:
A fundamental principle: each element of your code – whether it’s a QML component, a C++ class, or any other entity – must have a single responsibility. This means it must respond to a precise and unique need.
Why is this so important? How many times have we seen overloaded components or classes, where functions with multiple responsibilities pile up? This mixing of responsibilities makes code difficult to maintain, test and evolve. A QML component must do one thing, and one thing only.
It’s crucial to emphasize that business logic should never be managed by QML. The role of QML is to present the user interface and react to user interactions, but it must remain decoupled from business logic.
This separation is actually a pillar of modern software architectures, such as:
Visual example: In the diagram below, from Clean Architecture, the UI layer (IHM) is clearly positioned as an external plugin, distinct from the business domain.

Why adopt this approach?
In summary: Respect the principle “one entity, one responsibility”, and keep your QML exclusively dedicated to the interface. Business logic has its place elsewhere.
Ultimately, the golden rule (which applies to most languages):
“One QML component = one responsibility. Period.”
Concrete example:
// ProfilePage.qml (500 lines)
Item {
// 1. Displays user profile
// 2. Manages API connection
// 3. Validates form fields
// 4. Sends notifications
// ...
}
// ProfilePage.qml (50 lines)
Item {
ProfileHeader { user: userManager.currentUser() }
ProfileForm { onSubmit: userManager.save() }
NotificationPopup { }
}
userManager (C++).Why does this work?
ProfileHeader can be used elsewhere.A good component is like a black box: you only see what’s necessary to use it, the rest stays hidden. But to achieve this, it’s essential to think about inputs/outputs, what should be public and what should stay private before even writing a line of code.
The key? Adopt a user-centered approach for your component (whether that’s you in 6 months or a colleague). Ask yourself:
My little ritual (inspired by TDD): Before coding, I often write pseudo-code imagining my component already finished. For example:
// Pseudo-code: How would I like to use my component?
UserProfile {
user: backend.currentUser // Clear input
editable: isAdmin // Simple option
onSaved: showSuccess() // Explicit output
}
Then, I work to make this pseudo-code become reality. This method forces me to:
Result? A component simpler to use, test and maintain – and above all, that really responds to the needs of those who will use it.
Concrete example (taken and improved):
// UserProfile.qml - API designed "backwards"
Item {
id: root
// ===== PUBLIC API (what the user looks at) =====
//========================================================================
// Properties
//========================================================================
required property User user // User data (input)
required property bool editable // Edit mode (input)
//========================================================================
// Signals
//========================================================================
signal saved() // Save event (output)
signal error(string) // Error handling (output)
// ===== END PUBLIC API =====
// ===== IMPLEMENTATION (inside the black box) =====
//========================================================================
// Private elements
//========================================================================
QtObject {
id: internal
function validate()
{
/* Internal logic */
}
}
//========================================================================
// Child objects
//========================================================================
Column {
TextField { text: root.user.name; enabled: root.editable }
Button {
text: "Save"
onClicked: {
if (internal.validate())
{
root.saved(root.user?.name ?? "");
}
else
{
root.error("Invalid fields");
}
}
}
}
}
Why does this approach change everything?
In summary:
Best practices aren’t luxury or purist theory: they’re what makes a project stay alive and pleasant to maintain over time.
In QML as elsewhere, clear, decoupled and readable code means fewer bugs, more serenity and a team that moves forward without being afraid!
If you’ve made it this far, you’ve already taken a big step: awareness.
What’s next? Daily application – and that’s often where it gets difficult. Because between deadlines, technical debt and project reality, it’s easy to fall back into the traps I’ve described.
💡 To deepen the subject, I highly recommend this GitHub repository that gathers many tips and best practices in QML. You’ll find both style rules (naming, indentation, file organization) and design recommendations (view structuring, proper use of properties, performance optimization, etc.).
👉 QML Coding Guide – Furkanzmc
As a freelance developer and architect specialized in C++ / Qt / QML, I help my clients to:
👉 If you want to apply these principles to your project (or simply avoid it turning into spaghetti code 😉), I’d be happy to discuss it with you.
📩 Contact me