Pixel-craft PySide6/QML component kit — macOS first
Project description
pearl-kit
Pixel-craft PySide6/QML component kit. macOS first.
A curated set of QML primitives built on QtQuick.Templates, sourced from the shadcn/ui design language, with a three-theme token system and no framework abstractions.
What it is
pearl-kit is a small, opinionated QML component library for PySide6 desktop apps that care how they look on macOS. Instead of fighting Qt's default widget styling — or settling for Material Design — it treats components as a curated kit: a coherent token system, a handful of carefully-built primitives, shadcn-level attention to detail, and zero framework abstractions.
It's not trying to be the next big Python UI framework. It's trying to be the missing layer between PySide6 and a real-looking 2026 desktop app.
Why it exists
Python has had the "beautiful desktop app" problem for a decade. PyQt and PySide6 are powerful but default-ugly; the community has largely routed around them with Electron sidecars, webviews, and Flutter bridges. The recurring complaint in every "Python desktop UI in 2025" thread is some variant of "everything looks ten years out of date, and every Tailwind-level fix takes 1,000 lines of QSS."
pearl-kit is the answer that assumes:
- You want to stay in Python/QML for real reasons — NumPy, SciPy, PyTorch, domain libraries, or just because your backend is already there.
- You want the app to look like Linear or Raycast, not like a 2012 Qt demo.
- You don't want another framework — you want pixels you can drop in.
QML is the right substrate for this. It's GPU-accelerated, declarative, animation-first, and officially native on macOS since Qt 6. pearl-kit brings the shadcn/Linear design sensibility on top of it.
Install
pip install pearl-kit
or with uv:
uv add pearl-kit
Requirements: Python 3.11+, PySide6 6.8+, macOS or Linux.
Quick look
A sign-in card with three shipped components — Button, Input, Toggle — and one typography primitive (PearlText):
import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
import pearl_kit
app = QGuiApplication(sys.argv)
engine = QQmlApplicationEngine()
pearl_kit.register_qml(engine)
engine.loadData(b"""
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import PearlKit 1.0 as P
Window {
width: 480; height: 360; visible: true
color: P.Tokens.background
ColumnLayout {
anchors.centerIn: parent
spacing: P.Tokens.space.x4
width: 280
P.Input {
Layout.fillWidth: true
placeholderText: "you@domain.com"
}
RowLayout {
Layout.fillWidth: true
spacing: P.Tokens.space.x2
P.Toggle { checked: true }
P.PearlText {
text: "Remember me"
variant: "body"
}
Item { Layout.fillWidth: true }
}
P.Button {
Layout.fillWidth: true
text: "Sign in"
variant: "default"
}
}
}
""")
app.exec()
register_qml() adds the bundled QML directory to your engine's import path, so import PearlKit 1.0 resolves from anywhere.
Components
Eight primitives ship in v0.1.0. Each one is built on QtQuick.Templates.* with a full visual override — no reliance on Qt's default widget styling.
| Component | What it does | API surface | Docs |
|---|---|---|---|
Button |
Clickable action | 6 variants × 5 sizes, iconLeft/iconRight, loading, checkable | → |
Input |
Single-line text field | error state, iconLeft/iconRight, password mode | → |
Toggle |
On/off switch | 2 sizes, animated thumb, keyboard focus ring | → |
Select |
Non-editable dropdown | 2 sizes, error state, model-driven, grouped items | → |
Dialog |
Modal popover | title/description, footer slot, close button, enter/exit transitions | → |
CheckBox |
Multi-select box | error state, indeterminate (tristate) | → |
Stepper |
Numeric field with ± buttons | int + float modes, suffix, specialValueText, error state | → |
PearlText |
Typography primitive | 7 variants — title, heading, body, muted, label, code, mono | → |
Run python examples/gallery.py after a uv sync to see every variant live.
The token system
Three themes ship out of the box: Dark, DarkBlue, Light (default). Switch at runtime — every binding re-evaluates automatically:
P.Tokens.mode = P.Tokens.Light
Color tokens follow the shadcn/ui naming convention:
background foreground card cardForeground
popover popoverForeground primary primaryForeground
secondary secondaryForeground accent accentForeground
muted mutedForeground destructive destructiveForeground
success warning border input ring
elevation0 elevation1 elevation2 elevation3
Scale tokens:
| Category | API | Values |
|---|---|---|
| Radius | Tokens.radius.{sm,md,lg,xl,full} |
4, 6, 8, 12, 9999 |
| Spacing | Tokens.space.x{0..12} |
4-based scale: 0, 4, 8, 12, 16, 20, 24, 32, 40, 48 |
| Typography | Tokens.font.size.{xs..xxl} |
12, 14, 16, 18, 20, 24 |
| Font | Tokens.font.ui / .mono |
"SF Pro Display" / "SF Mono" |
| Shadow | Tokens.shadow.{sm,md,lg}{1,2} |
Two-layer shadcn-style |
| Motion | Tokens.motion.{fast,base,slow} |
150, 180, 260 ms |
Full reference: docs/tokens.
Roadmap
Shipped — v0.1.0
The first component tier, each one built on QtQuick.Templates with full visual override:
- Button — 6 variants × 5 sizes, icon slots, loading, checkable
- Input — single-line field with error state and icon slots
- Toggle — iOS-style switch, 2 sizes
- Select — non-editable dropdown with grouped items and keyboard navigation
- Dialog — modal popover with overlay, focus trap, and fade + zoom transitions
- CheckBox — indeterminate-capable, error-aware
- Stepper — numeric field with int + float modes and auto-repeat buttons
- PearlText — typography primitive covering title through mono variants
Next — layout primitives
GroupBox— titled section with optional collapseFormLayout/FormRow— label↔input alignment with hint and error slotsScrollArea— thin-scrollbar wrapper matching the token setSplitter— horizontal + vertical split with persistable sizesCard— elevated surface with radius, border, paddingSeparator— thin divider
Later
Feedback and navigation primitives — Slider, ProgressBar, Tooltip, Toast, Tabs, Menu, StatusBar, ListView, Badge, Avatar, CodeBlock, TextArea, and Spinner.
The scope is deliberately narrow. This is a component kit, not a framework — no reactivity engine, no router, no state management. Those belong in your app or in Qt Quick itself.
Philosophy
Three rules:
- macOS first, everything else follows. Tested on retina, SF Pro native, system conventions respected.
- Pixels over abstractions. No magic. Every component is one
.qmlfile you can read in under ten minutes. - Boring primitives, uncompromising execution. You should recognize every component from ten other design systems. What's different is the detail — the shadow layers, the focus rings, the transition curves.
Development
git clone https://github.com/omerdduran/pearl-kit
cd pearl-kit
uv sync --all-extras
uv run pytest # full suite (55 tests today)
uv run ruff check .
uv run pyright # strict mode
uv run mkdocs serve # live docs preview
Releases are fully automated. Pushing a v* tag triggers a GitHub Actions workflow that builds, publishes to PyPI via trusted publishing, and creates a GitHub Release with wheel + sdist + sigstore attestations.
Links
- Docs — https://omerdduran.github.io/pearl-kit
- PyPI — https://pypi.org/project/pearl-kit/
- Changelog — CHANGELOG.md
- Issues — https://github.com/omerdduran/pearl-kit/issues
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pearl_kit-0.2.0.tar.gz.
File metadata
- Download URL: pearl_kit-0.2.0.tar.gz
- Upload date:
- Size: 69.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34cb021626bd8eea4a26ae34df9d0e98b0a8469e0b271cf6c3b602b497b6e242
|
|
| MD5 |
b3b350b0d71826b7fcedbc47a2399e54
|
|
| BLAKE2b-256 |
ac73738f9c7c8712e23ac896f14b7938d08d603fe3a1e128bc5e1b543f803dbb
|
Provenance
The following attestation bundles were made for pearl_kit-0.2.0.tar.gz:
Publisher:
release.yml on omerdduran/pearl-kit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pearl_kit-0.2.0.tar.gz -
Subject digest:
34cb021626bd8eea4a26ae34df9d0e98b0a8469e0b271cf6c3b602b497b6e242 - Sigstore transparency entry: 1338818927
- Sigstore integration time:
-
Permalink:
omerdduran/pearl-kit@b5aba09fe0b60411a2d7fbfb2a775c7ff20bc9ad -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/omerdduran
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b5aba09fe0b60411a2d7fbfb2a775c7ff20bc9ad -
Trigger Event:
push
-
Statement type:
File details
Details for the file pearl_kit-0.2.0-py3-none-any.whl.
File metadata
- Download URL: pearl_kit-0.2.0-py3-none-any.whl
- Upload date:
- Size: 31.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65c542f957083d9b0c74103fae3bc9048b2ba3a118521269b07d6bb2efeb1dc2
|
|
| MD5 |
80e9f39165c759905f5832720403e133
|
|
| BLAKE2b-256 |
2caca63b2ee123d515b4ea5b22675f8b0ecb620608e1f6111bec219c1a9f328b
|
Provenance
The following attestation bundles were made for pearl_kit-0.2.0-py3-none-any.whl:
Publisher:
release.yml on omerdduran/pearl-kit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pearl_kit-0.2.0-py3-none-any.whl -
Subject digest:
65c542f957083d9b0c74103fae3bc9048b2ba3a118521269b07d6bb2efeb1dc2 - Sigstore transparency entry: 1338818929
- Sigstore integration time:
-
Permalink:
omerdduran/pearl-kit@b5aba09fe0b60411a2d7fbfb2a775c7ff20bc9ad -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/omerdduran
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b5aba09fe0b60411a2d7fbfb2a775c7ff20bc9ad -
Trigger Event:
push
-
Statement type: