Open source · SwiftUI design system · 0.2.0

Named values, composable behaviors, no magic numbers.

LubaUI is a SwiftUI package for building interfaces across five Apple platforms. Three choices separate it from the usual component library: behaviors are primitives any view can borrow, every value comes from a documented scale, and the system is queryable by a coding agent rather than only readable by a person.

Swift 5.9+ · Swift Package Manager · MIT · five Apple platforms

lubaui / theme preview

tokens resolvecomponents repaintmotion asks first

Most of the work went into the parts nobody demos: contrast ratios, touch targets, what the type scale does when someone enlarges it, and what the animations do when someone switches them off.

What’s different

Three choices that shape the rest.

None of these are exotic on their own. Holding all three at once is what changes how the system is built and how it gets used.

01

Legible to an agent

Semantic names, documented rationale, an MCP server, and a single reference file mean a coding agent can ask what a token is instead of inferring it from a hex value. Two tests fail the build if a theme role goes unread, or if a component reaches past the theme for a raw number.

02

Values derived, not picked

Spacing is a 4pt grid with stated ratios. Press feedback scales inversely with element size. The accent clears 4.5:1 in both modes. Where a number came from is part of its definition, which is also what makes it checkable.

03

Portable in two directions

One package covers iOS, macOS, watchOS, tvOS, and visionOS. And because a theme is data rather than a fork, the same components carry a different brand: LubaThemeColors.accented(_:) derives a full palette from a single colour.

37components
6behavior primitives
15semantic text roles
104tests, 2 structural

Release notes · 0.2.0 · Aug 2026

What changed most recently.

Earlier versions documented theming, accessibility, and localization more thoroughly than they implemented them. This release closes that gap and adds tests that fail if the wiring comes loose again.

Functional theming
Components resolve their colors, fonts, spacing, and radii from the theme in the environment, so changing the theme changes the UI.
Motion policy
One decision point combines the system Reduce Motion setting with the app’s configuration. Spinners breathe instead of spin; tab indicators cross-fade instead of slide.
Dynamic Type
15 semantic text roles, each anchored to an Apple text style, so the scale grows with the reader’s setting instead of sitting at fixed point sizes.
Localization
Built-in user-facing and accessibility strings ship in English and German, including the swipe actions the package provides.

Composability

Behaviors are modifiers, not features of a component.

Press, swipe, long-press, expand, shimmer, and glass attach to any view instead of living inside a button. Below are web approximations of the SwiftUI originals — the interaction is the point, not the pixels.

.lubaPressable().lubaSwipeable().lubaLongPressable().lubaExpandable().lubaShimmerable().lubaGlass()
.lubaPressable()

scale(0.97), spring(0.25s, 0.65 damping)

.lubaExpandable()

spring(0.35s, 0.75 damping)

.lubaSwipeable()
Swipe this row left

presets: .delete, .archive, .pin

.lubaShimmerable()

1.5s cycle, applied to any view

.lubaLongPressable()

progress ring, confirms destructive actions

The numbers

Where the values come from.

A token is only useful if you can say why it holds that value. These are the arguments behind the scale, the motion, and the defaults.

01

Every value has a name, and the name is enforced

Three token tiers with strict boundaries: raw values never reach a component, semantic tokens do. Two tests exist purely to guard the rule — they fail if a theme color is declared but never read, or if component code reaches past the theme for a raw dimension.

02

Feedback is relative to size

Smaller elements shrink more on press: compact 0.95, standard 0.97, prominent 0.98. A 44pt icon button needs exaggerated motion to feel responsive; a full-width card needs subtlety or it looks broken.

03

Accessibility is a default, not a mode

Sage accent #6B8068 light / #9AB897 dark, both above 4.5:1. Controls grow with the type scale, labels wrap rather than truncate, and small glyphs keep a 44pt target underneath them.

04

Reduced motion is not always less motion

A long-press progress ring keeps its real duration under Reduce Motion, because the fill is the countdown — shortening it would remove information rather than calm it.

On device

The showcase app.

Every component, primitive, and pattern in the package, running on a device rather than posed in a mockup.

LubaUI showcase — Composability
ComposabilityPrimitives attached to ordinary views
LubaUI showcase — Form patterns
Form patternsSignup, settings, and payment flows
LubaUI showcase — Theming
ThemingFour themes with live preview
LubaUI showcase — Typography
TypographyThe type scale in context
LubaUI showcase — Loading states
Loading statesSkeletons, spinners, and progress

Tooling

Two ways for an agent to ask.

Semantic naming and written rationale help a person and are close to required for an agent. An MCP server answers structured questions; a single reference file covers the rest — either way without reading every source file first.

MCP server

Ten tools for token and component lookup, spacing and radius validation, colour palettes, token suggestions, and migration planning from an existing system. Works with Claude Code, Cursor, and any MCP-compatible agent.

Terminal
claude mcp add lubaui -- npx lubaui-mcp
lookup_tokenlookup_tokenslookup_componentlookup_componentslookup_primitivevalidate_spacingvalidate_radiusget_color_palettesuggest_tokensplan_migration

llms.txt

A 651-line agent reference covering the architecture, every token, all component APIs, the primitives, and the usage patterns. One file, kept in step with the package.

Reference
shopofluba.de/lubaui/llms.txt
Read llms.txt

The library

37 components across five categories.

All of them resolve through the theme, and all of them compose with the primitives.

Interactive controls

LubaButtonLubaIconButtonLubaTextFieldLubaTextAreaLubaSearchBarLubaCheckboxLubaRadioLubaToggleLubaSliderLubaStepperLubaRatingLubaTabs

Layout & containers

LubaCardLubaSheetLubaDivider

Feedback & status

LubaToastLubaAlertLubaProgressBarLubaCircularProgressLubaSpinnerLubaBadgeLubaTooltip

Data display

LubaAvatarLubaIconLubaCircledIconLubaSkeletonLubaChipLubaLinkLubaMenu

Data visualization

LubaBarChartLubaGroupedBarChartLubaLineChartLubaMultiLineChartLubaPieChartLubaSparklineLubaChartSkeletonLubaChartLegend
ContentView.swift
import LubaUI

struct ContentView: View {
    @State private var name = ""
    @State private var notify = true

    var body: some View {
        VStack(spacing: LubaSpacing.lg) {
            LubaCard(elevation: .low) {
                LubaTextField("Name", text: $name)
                LubaToggle(isOn: $notify, label: "Notifications")
            }

            LubaButton("Save", style: .primary) { save() }
            LubaButton("Cancel", style: .ghost) { dismiss() }
        }
        .lubaPressable { navigate() }   // any view, tappable
        .lubaTheme(.accented(.indigo))  // and it repaints
    }
}

Open source, MIT licensed

Add it with Swift Package Manager.

Package.swift
.package(url: "https://github.com/ermanakar/LubaUI.git", from: "0.2.0")
iOS 16+macOS 13+watchOS 9+tvOS 16+visionOS 1+
View on GitHub