Mahina

A comprehensive Qt 6 / QML component library for desktop applications — fully themed, AOT-compiled, and keyboard-friendly.

0
Components
Qt 6
Target
Desktop
Platform
QML
Language
v0.45.0
Version

About

Mahina is a production-quality component library built on top of Qt 6 and QML. Every component is written to pass the qmlsc AOT strict-mode compiler, uses the shared Theme singleton for consistent design tokens (colours, spacing, radii, typography), and is designed exclusively for keyboard-and-mouse desktop interactions.

Components are registered as a proper QML module (import Mahina) so they drop into any Qt 6 project with a single find_package call. No JavaScript runtime, no web view, no third-party dependencies — just native Qt rendering at full GPU speed.

Motivation

Mahina started as the UI layer for Qub, a desktop SQL editor built with Qt 6. The goal was a consistent, well-functioning interface — something that felt cohesive rather than a collection of one-off widgets. Qt's built-in QtQuick.Controls covers the basics, but a real application quickly exposes the gaps: no data grid, no command palette, no chart primitives, no diff viewer, no sidebar sections.

After building enough components for Qub, the potential became clear. Other projects were already in mind that would need the same foundations, so the scope grew deliberately — from a handful of purpose-built widgets into a library that could serve as the UI layer for any desktop application.

The result is shared here because others building with Qt 6 are likely running into the same gaps. Mahina won't be the right fit for every project, but if you are looking for a single, coherent component set — one that covers everything from a humble Badge to a full SpreadsheetGrid, all sharing the same theme tokens, interaction conventions and AOT-safe QML patterns — it might save you from reimplementing the same primitives from scratch.

The library was built iteratively with AI assistance. The AI-made column in the component table confirms which components were AI-generated. The Human-reviewed column tracks which ones have been manually verified in a running application.

Installation

Via FetchContent Recommended

No local setup required — CMake fetches and builds Mahina automatically at configure time:

include(FetchContent)
FetchContent_Declare(Mahina
    GIT_REPOSITORY https://github.com/ajunior/mahina.git
    GIT_TAG        v0.45.0
)
FetchContent_MakeAvailable(Mahina)

target_link_libraries(MyApp PRIVATE
    Qt6::Quick Mahina Mahinaplugin Mahinaplugin_init)

As a subdirectory

Clone the repo alongside your project and add it directly:

add_subdirectory(mahina)

target_link_libraries(MyApp PRIVATE
    Qt6::Quick Mahina Mahinaplugin Mahinaplugin_init)

As an installed package

cmake -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x/gcc_64 \
               -DCMAKE_INSTALL_PREFIX=/usr/local
cmake --build build -j$(nproc)
cmake --install build

Then in your project's CMakeLists.txt:

find_package(Mahina REQUIRED)
target_link_libraries(MyApp PRIVATE Mahina::Mahina Mahina::Mahinaplugin)
qt_import_qml_plugins(MyApp)

Bundled assets

Mahina ships three font families as embedded Qt resources — no system installation required:

Register them with QFontDatabase before loading the QML engine:

#include <QFontDatabase>

// Inter and JetBrains Mono (UI and mono fonts)
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/InterVariable.ttf");
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/JetBrainsMonoVariable.ttf");

// Phosphor Icons (all six weights)
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/Phosphor-Regular.ttf");
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/Phosphor-Thin.ttf");
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/Phosphor-Light.ttf");
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/Phosphor-Bold.ttf");
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/Phosphor-Fill.ttf");
QFontDatabase::addApplicationFont(":/qt/qml/Mahina/assets/fonts/Phosphor-Duotone.ttf");

QML import

Register the import path at runtime, then use any component with a single import:

// main.cpp
engine.addImportPath(QStringLiteral("qrc:/qt/qml"));
// any .qml file
import Mahina

Button {
    text:    "Hello, Mahina!"
    variant: Button.Variant.Filled
    onClicked: console.log("clicked")
}

MahinaExtras

MahinaExtras is an optional companion library that ships alongside Mahina and adds native C++ capabilities that are not achievable in pure QML. Mahina itself stays dependency-free and AOT-safe; extras are opt-in.

It is not built by default. Enable it at configure time:

cmake -B build -DMAHINA_EXTRAS=ON

Linking

Add the extras targets next to the main Mahina targets in your CMakeLists.txt:

target_link_libraries(MyApp PRIVATE
    Qt6::Quick
    Mahina           Mahinaplugin           Mahinaplugin_init
    MahinaExtras     MahinaExtrasplugin     MahinaExtrasplugin_init
)

SyntaxHighlighter

Attaches to any CodeEditor via its textDocument property and applies token-level syntax colouring in real time. The colour scheme follows GitHub's dark/light palette and switches automatically when darkMode changes.

import Mahina
import MahinaExtras

CodeEditor {
    id: editor
    language: "sql"

    SyntaxHighlighter {
        document: editor.textDocument
        language: editor.language
        darkMode: Theme.dark
    }
}

Supported values for language:

Value(s)Language
"sql"SQL — keywords, types, strings, numbers, -- and /* */ comments
"qml" "js" "javascript" "ts" "typescript"QML / JavaScript / TypeScript — keywords, built-in types, property declarations, template literals
"python" "py"Python — keywords, decorators, f-strings, # comments
"json"JSON — keys (purple), string values, numbers, true / false / null
"bash" "sh" "shell"Bash / Shell — keywords, $VAR expansion, strings, # comments
"cpp" "c++" "c" "h" "hpp"C / C++ — keywords, STL types, preprocessor directives, // and /* */ comments
"java"Java — keywords, standard library types, annotations (@Override), text blocks, // and /* */ comments
"rust" "rs"Rust — keywords, primitive and std types, macros (println!), lifetimes, raw strings, // and /* */ comments
"go" "golang"Go — keywords, built-in types and functions, raw string literals (backtick), // and /* */ comments
"html" "htm"HTML — tags, attribute names and values, DOCTYPE, <!-- --> comments
"css" "scss" "less"CSS — at-rules, property names, values, hex colours, pseudo-classes, /* */ comments
"yaml" "yml"YAML — keys, quoted strings, anchors & aliases, scalar keywords, # comments
"xml" "svg" "xaml"XML — tags, attribute names and values, processing instructions, CDATA, <!-- --> comments

An empty or unrecognised language value is accepted — the highlighter applies no rules and the editor renders plain monochrome text.

Icons

Mahina bundles Phosphor Icons as embedded font assets — 900+ glyphs across six weights (Thin, Light, Regular, Bold, Fill, Duotone) with no external dependencies and zero network requests.

All glyph names are exposed through the Icons singleton as typed string constants. This avoids magic strings and gives you editor autocompletion:

import Mahina

Icon {
    name:  Icons.magnifyingGlass
    size:  24
    color: Theme.primary
}

The Icon component renders any glyph at any pixel size and color. The same Icons.* namespace is used everywhere an icon name is accepted — Button, NavigationRail, ContextMenu, and the rest all take an iconName property that resolves through Icons.

To browse available names, see the Icons component in the reference section below, or search for a specific icon using the component search above.

Theming

Every component in Mahina reads its colours from the Theme singleton. There are no hardcoded values inside components — swapping the palette in one place updates the entire UI instantly.

Default palettes

Mahina ships two built-in palettes: light and dark. The active mode is controlled by a single flag:

Theme.dark = true   // switch to dark
Theme.dark = false  // switch to light
Theme.dark = !Theme.dark  // toggle

All 262 components react to this change automatically with no additional wiring.

Custom color schema

Theme.load(obj) replaces any subset of tokens at runtime. You only provide the keys you want to change — everything else keeps the Mahina default. Mode-independent tokens (primary, success, warning, etc.) sit at the top level. Surface, border and text tokens that vary between light and dark are nested under light and dark keys:

Theme.load({
    primary:      "#7c3aed",
    primaryHover: "#6d28d9",
    primaryActive:"#5b21b6",
    primarySubtle:"#ede9fe",

    light: {
        background:   "#f8fafc",
        panel:        "#f1f5f9",
        surface:      "#ffffff",
        border:       "#e2e8f0",
        textPrimary:  "#0f172a",
        textSecondary:"#475569"
    },
    dark: {
        background:   "#0f172a",
        panel:        "#1e293b",
        surface:      "#1e293b",
        border:       "#334155",
        textPrimary:  "#f8fafc",
        textSecondary:"#94a3b8"
    }
})

Dark mode still works after a load() call — toggling Theme.dark switches between your custom light and dark values, not Mahina's originals.

Loading from a file

Any .json file with the same structure can be loaded at startup:

// main.qml
Component.onCompleted: {
    const xhr = new XMLHttpRequest()
    xhr.open("GET", Qt.resolvedUrl("theme.json"))
    xhr.onreadystatechange = function() {
        if (xhr.readyState === XMLHttpRequest.DONE)
            Theme.load(JSON.parse(xhr.responseText))
    }
    xhr.send()
}

Restoring defaults

Theme.reset() restores every token to the Mahina built-in values. Useful when letting users switch between multiple saved schemas at runtime:

Button { text: "Reset theme"; onClicked: Theme.reset() }

Token reference

Token Type In schema Default
primarycolortop-level#5B8DF6
primaryHovercolortop-level#4878E8
primaryActivecolortop-level#3A66D0
primarySubtlecolortop-level#EEF3FD
successcolortop-level#59A14F
warningcolortop-level#F28E2B
errorcolortop-level#E15759
infocolortop-level#2196E8
backgroundcolorlight / dark#EDEEF6 / #151922
panelcolorlight / dark#F4F4FA / #171D27
surfacecolorlight / dark#FFFFFF / #1E2430
surfaceVariantcolorlight / dark#F8F8FD / #222A36
bordercolorlight / dark#E2E5F0 / #313B4C
borderStrongcolorlight / dark#C8CEDF / #475569
textPrimarycolorlight / dark#1E2030 / #F4F7FB
textSecondarycolorlight / dark#697180 / #AAB4C4
textDisabledcolorlight / dark#9AA3AF / #7F8A9A
overlaycolorlight / dark#1E203066 / #00000099
shadowColorcolorlight / dark#1E2030 / #000000

Tokens

Every value that appears in a component — colour, size, spacing, radius, duration — comes from a named token on the Theme singleton. Using tokens instead of raw values means a single Theme.load() call repaints the entire UI.

Primary palette

TokenDefaultUsage
Theme.primary#5B8DF6Buttons, links, focus rings, active states
Theme.primaryHover#4878E8Hover state of filled primary elements
Theme.primaryActive#3A66D0Pressed / active state
Theme.primarySubtle#EEF3FDSelected row backgrounds, ghost hover fills
Theme.textOnPrimary#FFFFFFText / icons on filled primary backgrounds

Semantic colors

TokenDefaultSubtle variant
Theme.success#59A14F#EEF8ED — Theme.successSubtle
Theme.warning#F28E2B#FEF4E7 — Theme.warningSubtle
Theme.error#E15759#FEF0F0 — Theme.errorSubtle
Theme.info#2196E8#EFF7FE — Theme.infoSubtle

Surfaces & borders

These tokens have separate light and dark values and switch automatically with Theme.dark.

TokenLightDarkUsage
Theme.background#EDEEF6#151922Page / window background
Theme.panel#F4F4FA#171D27Sidebars, toolbar strips
Theme.surface#FFFFFF#1E2430Cards, dialogs, popovers
Theme.surfaceVariant#F8F8FD#222A36Alternate rows, input backgrounds
Theme.overlay#1E203066#00000099Modal / drawer scrim
Theme.border#E2E5F0#313B4CDividers, input outlines
Theme.borderStrong#C8CEDF#475569Emphasized borders, focus rings

Text

TokenLightDarkUsage
Theme.textPrimary#1E2030#F4F7FBHeadlines, labels, body copy
Theme.textSecondary#697180#AAB4C4Captions, hints, metadata
Theme.textDisabled#9AA3AF#7F8A9ADisabled controls, placeholder text
Theme.shadowColor#1E2030#000000DropShadow filter color

Spacing

4 px base grid. Use these for margins, paddings and gaps — never raw pixel values.

TokenValueScale
Theme.sp14 px
Theme.sp28 px
Theme.sp312 px
Theme.sp416 px
Theme.sp520 px
Theme.sp624 px
Theme.sp832 px
Theme.sp1040 px
Theme.sp1248 px
Theme.sp1664 px

Border radius

TokenValuePreviewUsage
Theme.radiusXs2 pxBadges, tags
Theme.radiusSm4 pxButtons, inputs, small cards
Theme.radiusMd8 pxCards, popovers
Theme.radiusLg12 pxDialogs, large panels
Theme.radiusXl16 pxSheets, drawers
Theme.radiusFull9999 pxPills, avatar circles

Typefaces

Mahina bundles two fonts as embedded assets — no network requests, no system dependency.

TokenDefaultUsage
Theme.fontFamily Inter (variable, v4.0) All UI text — labels, body, headings
Theme.fontFamilyMono JetBrains Mono (variable, v2.3) Code blocks, terminal, editors, kbd shortcuts

Both tokens are writable and included in Theme.load(), so you can swap to any font the host app registers:

// in main.cpp
QFontDatabase::addApplicationFont(":/fonts/Geist-Variable.ttf");

// in QML
Theme.load({ fontFamily: "Geist", fontFamilyMono: "Fira Code" })

Type scale

TokenSizePreview
Theme.textXs11 pxThe quick brown fox
Theme.textSm13 pxThe quick brown fox
Theme.textBase15 pxThe quick brown fox
Theme.textLg17 pxThe quick brown fox
Theme.textXl20 pxThe quick brown fox
Theme.text2xl24 pxThe quick brown fox
Theme.text3xl30 pxThe quick brown fox
Theme.text4xl36 pxThe quick brown fox

Font weights

TokenValuePreview
Theme.weightLight300Mahina UI
Theme.weightRegular400Mahina UI
Theme.weightMedium500Mahina UI
Theme.weightSemibold600Mahina UI
Theme.weightBold700Mahina UI

Animation

TokenValueUsage
Theme.durationFast80 msHover colour transitions, small state changes
Theme.durationNormal150 msMost enter/leave transitions
Theme.durationSlow250 msLarger layout changes, page transitions
Theme.easingOutCubicDefault easing curve for all animations

Elevation

Three shadow levels for use with Qt's DropShadow layer effect. Each level exposes a blur radius, Y offset, and opacity multiplier paired with Theme.shadowColor.

LevelBlurY offsetOpacityUsage
shadowSm*6 px1 px0.06Cards, inputs
shadowMd*16 px4 px0.10Dropdowns, popovers
shadowLg*32 px8 px0.14Dialogs, floating panels

Components

ComponentCategoryDescriptionAI-madeHuman-reviewed

No components match.

Reference

Per-component description, implementation notes, and best-fit scenarios for all 262 components.

Example

The repository ships a self-contained kitchen-sink app that renders live instances of the components against a real Qt runtime. It is the fastest way to see how everything looks and behaves together, and to verify that your build environment is set up correctly.

Building

git clone https://github.com/ajunior/mahina.git
cd mahina
cmake -B build -DCMAKE_PREFIX_PATH=/path/to/Qt/6.x/gcc_64
cmake --build build -j$(nproc)
./build/example/bin/MahinaExample

What's inside

A 1000 × 680 window with a NavBar, a Sidebar, and 18 pages that group components by theme. Each page is a scrollable Flickable with live, interactive instances — not screenshots.

Page What it covers
ComponentsButtons, Badges, Inputs, Cards, Avatars, Chips, Toggles, Checkboxes, Sliders, Dropdowns
NotificationsAlert, Banner, Callout, Toast, Notification, Badge counts
TypographyFont scale, weights, colour tokens, Kbd, CodeBlock
IconsIcon grid, all six Phosphor weights side by side
FormsFormField, DatePicker, TimePicker, OTPInput, RatingInput, TagInput, MultiSelect
DataDataGrid, VirtualList, Tree, JsonViewer, DataList, Pagination
ExtendedCommandPalette, Tour, Kanban, EventCalendar, DashboardGrid, Carousel
MoreDrawer, Sheet, Dialog, Popover, HoverCard, ContextMenu, Tooltip
AdvancedSplitPane, Resizable, Dock, FloatingToolbar, FloatingIsland, Sortable
SettingsPreferencesLayout, Toggle groups, KeybindingInput, ShortcutManager
Display & DataMediaPlayer, ImageGallery, VideoPlayer, QRCode, Timeline, ActivityFeed
Charts & LayoutAreaChart, GanttChart, TreeMap, Histogram, LiveChart, KPICard, MetricCard
Charts & NavRadarChart, BubbleChart, ScatterPlot, MenuBar, Ribbon, NavigationRail
Inputs & ChartsColorPicker, FontPicker, CronEditor, NodeEditor, KeyframeEditor, SpreadsheetGrid
Social & FeedbackChat, CommentThread, ReactionBar, PresenceList, FeedbackWidget, Confetti
Social & ChartsVideoCallTile, NetworkGraph, SankeyDiagram, WordCloud, CorrelationMatrix
Nav & InputsCommandMenu, TabDock, AppSwitcher, DragDropList, TransferList, FilterBar
Editors & ToolsCodeEditor, DiffEditor, HexViewer, Terminal, RichTextEditor, MarkdownEditor

Dark mode

Press D at any time to toggle between light and dark. The NavBar also exposes a toggle button. All 262 components respond to the Theme.dark flag with no additional wiring.