Logos Design System
The Qt/QML design system for Logos applications. Two QML modules — Logos.Theme for design tokens and Logos.Controls for themed components — plus a storybook for browsing them and a unit test suite.
nix build # the QML library (what consumers depend on)
nix run # browse components + token catalog (alias of .#storybook)
nix run .#tests # run the QML unit tests
nix flake check # CI: build + test
What's inside
Logos.Theme — design tokens
| File | Provides |
|---|---|
Theme.qml |
Singleton: Theme.palette, Theme.spacing, Theme.typography, theme switching |
ColorPalette.qml |
Raw color values (gray, orange, red, green, …) — single source of truth |
DarkTheme.qml |
Semantic palette mapped from ColorPalette: text, background, primary, success, border, surface, surfaceRaised, borderHairline, etc. |
Spacing.qml |
Spacing scale (tiny:4 … xxlarge:40) and corner radii (radiusSmall:4 … radiusPill:999) |
Typography.qml |
Public Sans font loaders, weights, type sizes |
Logos.Controls — themed components
Components are split into two groups. Designed controls have their final styling applied. Placeholders are themed minimally with current tokens (correct fonts, colors, radii) and ship with full tests + a storybook page, but their visuals will be replaced when the Figma spec for that control lands.
Designed: LogosButton, LogosCheckbox, LogosComboBox, LogosIconButton, LogosPaginator, LogosSearchBar, LogosTabBar, LogosTabButton, LogosTable + LogosTableColumn, LogosText, LogosTextField.
Placeholder: LogosDialog, LogosFrame, LogosGroupBox, LogosMenu + LogosMenuItem, LogosProgressBar, LogosRadioButton, LogosScrollBar, LogosSlider, LogosSpinBox, LogosSpinner, LogosSwitch, LogosTextArea, LogosToolBar + LogosToolButton.
Open the storybook to see public API, props, and live examples for each. Internal items exposed via *Item aliases (e.g. indicatorItem, labelItem) are for inspection by tests and tooling — read-only handles.
Designed vs placeholder
Each storybook page declares property bool designed — true (the default) for final controls, false for placeholders. Placeholders are listed under the Controls (not designed) sidebar section in the storybook so reviewers and consumers can see at a glance what's finalised vs. pending.
When a placeholder graduates to designed:
- Replace the visuals in
src/qml/Logos/Controls/Logos<X>.qmlwith the final spec. - In
storybook/pages/Logos<X>Page.qml, flipproperty bool designed: false→true(or remove the line, sincetrueis the default). - In
storybook/main.qml, move the entry from the"Controls (not designed)"section to the"Controls"section so it appears at the top. - Add or update tests in
tests/tst_Logos<X>.qmlfor any new design-specific behaviour. - If a Figma URL exists, set
property string figmaUrl: "..."on the page root so the storybook header surfaces an "Open in Figma" button.
Repo layout
.
├── flake.nix thin orchestrator — inputs, outputs, devShell
├── nix/
│ ├── common.nix shared src + buildInputs + cmake flags
│ ├── library.nix packages.default (the QML library)
│ ├── storybook.nix packages.storybook (host app)
│ └── tests.nix packages.tests (test runner)
├── src/qml/Logos/
│ ├── Theme/ Logos.Theme module
│ └── Controls/ Logos.Controls module
├── storybook/ catalog browser host app + per-page QML
│ ├── main.cpp + main.qml host with sidebar, dark/light toggle, hot-reload
│ └── pages/ one .qml per token group / control
├── tests/ QtQuickTest unit tests
│ ├── main.cpp quick_test_main_with_setup wiring
│ ├── tst_*.qml one file per component (LogosTabBar, LogosButton, …)
│ ├── test-icon.png 1×1 RGBA PNG bundled via test-resources.qrc
│ └── CMakeLists.txt
├── CMakeLists.txt top-level: opt-in storybook + tests via flags
└── .github/workflows/ci.yml build + tests + storybook smoke
Build & run
Using the design system from a consumer (recommended)
The QML library is published as packages.${system}.default. Each consumer's flake declares it as an input and copies the modules into its own $out/lib/Logos/:
# consumer flake.nix
inputs.logos-design-system.url = "github:logos-co/logos-design-system";
inputs.logos-design-system.inputs.nixpkgs.follows = "logos-cpp-sdk/nixpkgs";
# consumer nix/app.nix installPhase:
cp -r "${logos-design-system.packages.${system}.default}/lib/Logos" "$out/lib/"
Then in QML:
import Logos.Theme
import Logos.Controls
Rectangle {
color: Theme.palette.background
LogosButton {
text: "Hello"
onClicked: console.log("clicked")
}
}
For dev-time override (work on the design system + your app in parallel), set LOGOS_DESIGN_SYSTEM_ROOT to the design-system's result/ so your app picks up the live source. See logos-basecamp/run-dev.sh for the canonical pattern.
Building locally
nix build # QML library only — drops .qml files into result/lib/Logos/
nix build .#storybook # storybook host app
nix build .#tests # build + run unit tests via doCheck
nix flake check # same as `nix build .#tests`
For a non-nix CMake build:
cmake -B build -GNinja -DLOGOS_DS_BUILD_STORYBOOK=ON -DLOGOS_DS_BUILD_TESTS=ON
cmake --build build
(cd build && ctest --output-on-failure)
./build/storybook/LogosStorybook
The CMake options are off by default — the default nix build ships only the QML files.
Storybook
Interactive catalog of every token and control. Use it to:
- Browse all
Theme.palette.*colors (with names, hex values, swatches) - See the
Theme.spacing.*scale visualized as bars + radii - Sample every typography size and weight
- Poke each control's states with live knobs (text input, toggles, sliders)
- Open the matching Figma frame for any page (when its
figmaUrlis set)
nix run # bare run — apps.default points at the storybook
nix run .#storybook # explicit form (same thing)
nix build .#storybook && ./result/bin/LogosStorybook # build then launch manually
Hot reload during development
The storybook host app watches the pages/ directory and the design-system QML source tree. Edits to any .qml file land within ~150 ms — no rebuild needed.
The header has manual Reload and Light/Dark buttons. The "Open in Figma" button appears on pages where the page's figmaUrl property is set.
Adding a new page
Create storybook/pages/<NewControl>Page.qml, register it in the pages array in storybook/main.qml, run the storybook. Optionally set figmaUrl: "https://figma.com/file/.../?node-id=..." on the page root so the toolbar's Figma link appears.
Tests
QtQuickTest unit tests, one file per component. Run via nix flake check in CI, or directly while iterating:
nix run .#tests # full output, all PASS/FAIL lines
nix flake check # CI form — silent on success
For verbose runs:
./result/bin/LogosDesignSystemTests # default output
./result/bin/LogosDesignSystemTests -v2 # extra signal/timing info
./result/bin/LogosDesignSystemTests -functions # list every test
./result/bin/LogosDesignSystemTests -xunitxml -o tests.xml # JUnit XML for CI
Adding a new test
- Add
tests/tst_<Component>.qmlwith aTestCase { … }block. Useid: tc, setwidth/height, andwhen: windowShown. - The runner picks it up automatically —
tests/main.cppadds the design-system QML import path;tst_*.qmlfiles intests/are auto-discovered. - For new components, expose internal items via
readonly property alias <name>Item: <id>so tests can assert on them without going throughfindChild.
Notes on test design
- Prefer
tryCompare(obj, "prop", value)overtryVerify(function() { … }). The closure-based form has had reliability issues across Qt versions. Item.visiblereturns effective (ancestor-chain) visibility — unreliable in offscreen test runs. Test what you actually care about (existence, position, size, property round-trips) instead of asserting onvisible.- For mouse interactions, emit
mouseAreaItem.clicked(null)directly. SynthesizedmouseClickevents do not always reach innerMouseAreas throughControlon offscreen QPA.
Tokens at a glance
Theme.palette.* semantic colors:
- Surfaces & backgrounds:
background,backgroundSecondary,backgroundElevated,surface,surfaceRaised,backgroundButton,backgroundInset - Text:
text,textSecondary,textTertiary,textPlaceholder,textMuted - Borders:
border,borderSubtle,borderHairline,borderInteractive,borderDark - Primary / accent:
primary,primaryHover,primaryPressed,primarySoft - Status:
success,error,warning,info,notification - Interactive states:
hover,pressed,disabled,focus - Overlays:
glassOverlay,glassStrong,overlayDark,overlayLight,overlayOrange
Theme.spacing.*: tiny:4, small:8, medium:12, large:16, xlarge:20, xxlarge:40, plus radiusSmall:4, radiusMedium:6, radiusLarge:8, radiusXlarge:16, radiusPill:999.
Theme.typography.*: weightRegular:400, weightMedium:500, weightBold:700, sizes secondaryText:12, primaryText:14, titleText:30, mainTitleText:256.
The storybook's Colors / Spacing / Typography pages show the full set with names — open it whenever you need to find a token.
Requirements
- Qt 6.x (qtbase + qtdeclarative)
- CMake 3.16+
- C++17
Contributing
- Add colors to
ColorPalette.qmlfirst, then map to a semantic name inDarkTheme.qml. The semantic name is what consumers should reference (Theme.palette.surfaceRaised, notTheme.colors.gray360). - For new controls: extend the right Qt Quick Controls base (
Control,TabButton, etc.), expose only what consumers actually need, and add areadonly property alias <name>Item: <id>for any internal item that tests / tooling need to inspect. - Decide designed vs placeholder. If the Figma spec is final, ship as a designed control: register under
"Controls"instorybook/main.qmland either omitdesignedon the page (defaults to true) or set it totrueexplicitly. If the spec is pending, ship as a placeholder: theme it minimally withTheme.palette.*/Theme.spacing.*, register under"Controls (not designed)", and setproperty bool designed: falseon the page. Both kinds get the same testing + storybook treatment so promotions are mechanical. - Add a storybook page for any new control (
storybook/pages/Logos<X>Page.qml) and a unit test (tests/tst_Logos<X>.qml). - Promoting a placeholder to designed — see the Designed vs placeholder section above for the four-step flip.
- Run
nix flake checkbefore pushing — CI runs the same.