English | 简体中文
qt-md3 is a C++ component library that reproduces Material Design 3
(including M3 Expressive) as native Qt Widgets. Everything is
hand-painted with QPainter / QProxyStyle — no QSS, no QStyleSheet.
The goal is not a loose "Material-flavoured" look. Token values, component behavior, states, and motion are aligned item by item with the official design system:
- Design system: https://m3.material.io/
- Token truth (numeric values are taken from here, never from memory): https://github.com/material-components/material-web/tree/main/tokens
- Color algorithms: https://github.com/material-foundation/material-color-utilities
Version 0.1.0 — first public release. Stage 1 (all 36 official MD3
families) and Stage 2 (55 Qt extension headers) are landed.
| Metric | Value |
|---|---|
| Stage 1 MD3 families | 36 / 36 ported |
| Stage 2 Qt extensions | 55 public headers (appendix D closed) |
| Public headers | 106 under src/widgets/ |
| Foundation modules | 21 |
| CTest entries | 94 (all green) |
| Gallery pages | 55 + three product showcases |
| Supported Qt | 5.15.2+ and 6.5.0+ |
Progress is tracked in docs/md3-coverage.md. Current state: docs/project-status.md. Screenshot gallery: docs/screenshots/.
- Stage 1 — MD3 official components. All 36 component families from https://m3.material.io/components, with every property, variant, size, shape, state, token, motion spec, and keyboard behavior.
- Stage 2 — Qt desktop extensions. Common Qt controls that MD3 has no spec for, redesigned with Stage 1 tokens and semantics.
Stage 2 does not start until every Stage 1 family is green across all nine
columns. A CTest gate (TestMd3CoveragePolicy) fails the build if a Stage 2
public component appears early.
The 36 Stage 1 families
Buttons · Button groups · Icon buttons · FABs · Extended FABs · FAB menu · Split buttons · Segmented buttons · Badges · Progress indicators · Loading indicator · Snackbar · Tooltips · Cards · Dialogs · Bottom sheets · Side sheets · Carousel · Divider · Lists · App bars · Toolbars · Navigation bar · Navigation rail · Navigation drawer · Tabs · Checkbox · Chips · Date pickers · Menus · Radio button · Sliders · Switch · Time pickers · Text fields · Search
Stage 2 Qt extensions (55 headers)
MdScrollBar · MdSelect · MdMessageBox · MdScrollArea · MdTable ·
MdAvatar · MdWindow · MdFileDialog · MdInputNumber · MdScaffold ·
MdMenuBar · MdStatusBar · MdSplitter · MdPagination · MdEmptyState ·
MdSkeleton · MdSteps / MdStepper · MdUpload · MdRate · MdFlex ·
MdGrid · MdSpace · MdCollapse · MdNotification · MdTree /
MdTreeSelect · MdStatistic · MdPopover / MdPopconfirm ·
MdAutoComplete · MdColorPicker · MdDockWidget / MdDockManager ·
MdDescriptions · MdTimeline · MdImage · MdWatermark · MdTransfer ·
MdMentions · MdBackTop · MdInputDialog · MdStackedWidget · MdResult ·
MdBreadcrumb · MdQRCode · MdLog · MdCascader · MdPlainTextEdit ·
MdMasonry · MdDrawer · MdTour / MdCoachMark · MdRibbon ·
MdNav / MdNavItem · MdAffix / MdAnchor
Everything lives in the md namespace, class prefix Md. Include the widget
header and link qt-md3::qt-md3.
#include <MdDesign.h>
#include <MdTheme.h>
#include <MdButton.h>
#include <MdFab.h>
int main(int argc, char *argv[])
{
md::MdDesign::configureHighDpi(); // before QApplication
QApplication app(argc, argv);
md::MdDesign::initialize(&app); // fonts, language, base font, RTL
auto *button = new md::MdButton;
button->setLabelText(QStringLiteral("Filled"));
button->setColorStyle(md::ButtonColorStyle::Filled);
button->setSize(md::ButtonSize::Medium); // Expressive xsmall…xlarge
// Theme is a singleton; every widget re-paints on themeChanged.
md::MdTheme::instance().setThemeMode(md::ThemeMode::Dark);
md::MdTheme::instance().setSeedColor(QColor(0x6750A4));
md::MdTheme::instance().setDynamicColor(true);
md::MdTheme::instance().setContrastLevel(md::ContrastLevel::High);
...
}| Convention | Rule |
|---|---|
| Namespace / prefix | md / Md (MdButton, MdTheme, MdColorRole) |
| Enums | named 1:1 with the official semantics; every enum ends with a Count sentinel |
| Configurable items | every one is a Q_PROPERTY with a NOTIFY signal |
| States | enabled / hovered / focused / pressed / disabled / selected / dragged / loading, as applicable |
| Themes | light / dark, seed change, contrast level, density, RTL, font switch |
| Painting | QPainter / QProxyStyle only — QSS is forbidden and gated |
| Header | Role |
|---|---|
MdDesign |
configureHighDpi() + initialize(&app) startup entry point |
MdTheme |
singleton: mode, seed, dynamic colour, contrast, density, RTL, themeChanged |
MdTokens |
md.ref.* / md.sys.* / md.comp.* token layers |
MdColorScheme |
49 colour roles, light and dark, including the *-fixed families |
MdDynamicColor |
nine scheme variants resolved through the ColorSpec2021 solver |
MdTypeScale |
15 baseline + 15 emphasized styles, script-category line height |
MdShape |
corner scale, radius interpolation, path morph |
MdMotion |
10 easings, 16 durations, six Expressive spring slots |
MdIcon |
Material Symbols (variable font, four axes) + classic SVG fallback |
MdStyleBase |
QProxyStyle hub: paint filters, theme subscriptions |
A component is only “done” when its Definition of Done holds — see CONTRIBUTING.md.
Selected light/dark captures live in docs/screenshots/:
the three product showcases (Mail, Music, Settings), the dynamic-colour page,
and a couple of component pages. Full-page PNGs of every gallery page can be
regenerated at any time:
cmake --build build
build/examples/qt-md3-example --language en --theme light --screenshot build/shots-light
build/examples/qt-md3-example --language en --theme dark --screenshot build/shots-darkEach page produces two images: <nn>-<slug>.png (the window) and
<nn>-<slug>-full.png (the page grown to its heightForWidth()). Prefer the
-full one — the window shot only ever shows the first screenful. Capture
from the native platform when text matters; QT_QPA_PLATFORM=offscreen
renders tofu boxes on Windows.
Interactive gallery:
tools/run-gallery.sh # open the gallery
tools/run-gallery.sh --theme dark --dynamic- Tokens over literals. Every color, radius, size, spacing, and duration comes from the theme. No magic values inside components.
- Tonal elevation, not shadow. Layering is expressed with tonal surfaces; shadows appear only where the spec explicitly requires them.
- State layer ≠ ripple. Hover and focus are static overlays; ripple only spreads on press and clips to the current shape.
- Shape morph. Radii come from shape tokens and tween at runtime.
- Dynamic color. Light and dark are tone mappings of one
DynamicScheme; changing the seed color regenerates every role. - Script-aware typography. Line height adapts to the language script height class (Chinese is taller than Latin).
- Expressive motion. Spring physics coexists with classic easing curves.
- No QSS. Painting is
QPainteronly, enforced by a gate.
| Qt 5.15.2+ | Qt 6.5.0+ | |
|---|---|---|
| Compilers | MinGW 8.1+, MSVC 2019+, Clang 10+ | MinGW 11+, MSVC 2019+, Clang 12+, AppleClang 14+ |
| Modules | Core, Widgets, Svg (Test for tests) |
same |
| CMake | 3.16+ | 3.16+ |
| C++ | C++17 | C++17 |
| OS (CI) | Linux, Windows | Linux, Windows, macOS |
| Dynamic colour | Yes | Yes |
| Expressive springs | Yes | Yes |
| Material Symbols fonts | Yes (Qt 5 renders the default instance; static weights preferred) | Yes |
| RTL | Yes | Yes |
CI matrix: Linux Qt 5.15.2 Debug ASan+UBSan, Linux Qt 5.15.2 shared Release, Windows Qt 5.15.2, Windows Qt 6.7.3, Windows Qt 6.7.3 shared Release, macOS Qt 6 shared Release.
cmake --preset windows-msvc-qt6-debug
cmake --build --preset qt6-debug
ctest --preset qt6-debugOr configure manually:
cmake -S . -B build -DQT_MD3_BUILD_EXAMPLES=ON -DQT_MD3_BUILD_TESTS=ON
cmake --build build --config Debug --parallel
ctest --test-dir build -C Debug --output-on-failureWith the Qt 6 MinGW kit:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_COMPILER=C:/Qt/Tools/mingw1310_64/bin/g++.exe \
-DCMAKE_PREFIX_PATH=C:/Qt/6.9.1/mingw_64 \
-DQT_MD3_BUILD_EXAMPLES=ON -DQT_MD3_BUILD_TESTS=ON
cmake --build build
ctest --test-dir build --output-on-failureWith the Qt 5.15 kit, point QT5_CMAKE_PREFIX_PATH (or CMAKE_PREFIX_PATH)
at the Qt 5 lib/cmake directory so CMake does not pick up Qt 6.
The library is static by default; pass -DBUILD_SHARED_LIBS=ON for a shared
build. On Windows every executable gets the Qt runtime, the C++ runtime and the
platforms/ + styles/ plugins copied next to it, so the example in build/
runs by double-click without a Qt-aware PATH. cmake --install additionally
uses windeployqt. Pass -DQT_MD3_DEPLOY_EXAMPLE=OFF to skip both.
cmake --install build --config Releasefind_package(qt-md3 CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE qt-md3::qt-md3)The installed package re-checks the minimum Qt version on the consumer side.
src/core/ tokens, theme, color algorithms, type scale, shape, motion, icons
src/styles/ Md*Style painting classes
src/widgets/ public components (Md*.h)
src/private/ internal helpers (not exported)
examples/ the component gallery app
tests/ QTest + CTest, including the policy gates
resources/ icons, fonts, .qrc
docs/ status, audit, specs, TODOs, screenshots
| Gate | Enforces |
|---|---|
TestMd3NoQss |
No QSS anywhere in src/, examples/, tests/, resources/ |
TestMd3CoveragePolicy |
All 36 families tracked; no Stage 2 components before Stage 1 is green |
TestMd3SourceEncoding |
Every source file is valid UTF-8; no UTF-8 literal is decoded as Latin-1 |
TestMd3Deployment* |
Every executable starts with no Qt on PATH — the double-click case |
See CONTRIBUTING.md for the development flow, the component Definition of Done, the Qt 5 portability checklist, and the build/test commands. Please also read AGENTS.md and the Code of Conduct.
MIT. Ported or bundled third-party assets (Material Design tokens, Material Color Utilities, Material Symbols, Roboto) keep their own licenses — see NOTICE.md. This is an independent community project, not affiliated with or endorsed by Google LLC or The Qt Company Ltd.