ermanakar/lubaui
**Ship beautiful SwiftUI apps without thinking about design.**
30-Second Demo
import SwiftUI
import LubaUI
struct ContentView: View {
@State private var email = ""
@State private var agreed = false
var body: some View {
VStack(spacing: LubaSpacing.lg) {
Text("Create Account")
.font(LubaTypography.title)
.foregroundStyle(LubaColors.textPrimary)
LubaTextField.email(text: $email)
LubaCheckbox(isChecked: $agreed, label: "I agree to the terms")
LubaButton("Get Started", style: .primary, isDisabled: !agreed) {
createAccount()
}
}
.padding(LubaSpacing.xl)
}
}No color codes. No animation curves. No magic numbers. It just looks and feels like a real app.
Installation
Swift Package Manager
Xcode: File → Add Package Dependencies → Enter:
https://github.com/ermanakar/LubaUIPackage.swift:
dependencies: [
.package(url: "https://github.com/ermanakar/LubaUI", from: "0.1.0")
]Why LubaUI
Built for AI-Assisted Development
Every token, every constant, every API in LubaUI is designed to be understood by your AI coding assistant. Semantic naming (LubaColors.textPrimary, not gray900), strict tokenization (no magic numbers), and documented rationale for every design decision. When you pair with an AI, it doesn't guess — it knows this system.
One Import, Whole Design Language
You get colors, typography, spacing, motion, haptics, accessibility, and 35+ components that all share the same DNA. Your settings screen looks like it belongs with your onboarding flow because they're built from the same tokens.
The Details Are Done
The hard stuff is already decided:
- 0.97 press scale — not 0.98 (too subtle), not 0.95 (too cartoonish)
- Spring animations tuned to feel alive, not mechanical
- 4pt spacing grid for mathematical rhythm
- 44pt touch targets for accessibility compliance
- Adaptive colors that look right in light and dark mode
You don't have to make these decisions. They're already made, and they're good.
Primitives — Make Anything Interactive
This is the core idea. Behaviors aren't locked inside components — they're modifiers you can attach to any view.
.lubaPressable() — Tap anything
// A card that feels like a button
LubaCard {
Text("Tap me")
}
.lubaPressable { print("Tapped!") }
// An image that responds to touch
Image("hero")
.lubaPressable(scale: 0.98) { showDetail = true }.lubaSwipeable() — Swipe actions on any row
// Swipe to delete
MessageRow()
.lubaSwipeable(trailing: [.delete { removeItem() }])
// Multiple actions
MessageRow()
.lubaSwipeable(
leading: [.pin { pinMessage() }],
trailing: [.archive { archive() }, .delete { delete() }]
).lubaLongPressable() — Long press with progress
// Destructive action with visual confirmation
Image(systemName: "trash")
.lubaLongPressable { confirmDeletion() }
// With progress ring
LubaCard { content }
.lubaLongPressable(showProgress: true, duration: 1.0) {
confirmDeletion()
}.lubaShimmerable() — Loading state for any view
// Shimmer anything while loading
Image("hero")
.lubaShimmerable(isLoading: isLoading)LubaExpandable — Accordion behavior
LubaExpandable(isExpanded: $isOpen) {
Text("FAQ Question")
} content: {
Text("The answer goes here")
}Components
Interactive Controls
| Component | What It Does | |-----------|-------------| | LubaButton | Primary, secondary, ghost, destructive, subtle. Loading states. Icons. | | LubaTextField | Labels, icons, error states. Convenience: .email(), .secure() | | LubaTextArea | Multi-line text editor with character counter and limits | | LubaSearchBar | Search input with cancel button and submit handler | | LubaCheckbox | Animated checkmark with label | | LubaRadio | Radio button groups | | LubaToggle | iOS-style toggle switch | | LubaSlider | Value slider with optional labels | | LubaStepper | Numeric +/- adjuster with configurable range and step | | LubaRating | Star rating control with read-only display mode | | LubaTabs | Segmented control with matched geometry animation | | LubaIconButton | Icon buttons with 44pt touch targets |
Layout & Containers
| Component | What It Does | |-----------|-------------| | LubaCard | Container with elevation levels. Compose with .lubaPressable() | | LubaSheet | Bottom sheets with size presets and drag indicator | | LubaDivider | Horizontal/vertical dividers with optional labels |
Feedback & Status
| Component | What It Does | |-----------|-------------| | LubaToast | Info, success, warning, error notifications with auto-dismiss | | LubaAlert | Inline notification banner with semantic styles | | LubaProgressBar | Linear progress indicator | | LubaCircularProgress | Circular progress indicator | | LubaSpinner | Arc, pulse, dots, and breathe loading styles | | LubaBadge | Status badges with semantic colors | | LubaTooltip | Contextual help popup with auto-dismiss |
Data Display
| Component | What It Does | |-----------|-------------| | LubaAvatar | Image or initials with size variants | | LubaIcon | Standardized icon sizing with semantic colors | | LubaCircledIcon | Icons with circular backgrounds | | LubaSkeleton | Shimmer loading placeholders (text, circle, card, row) | | LubaChip | Dismissible filter/tag pill with selection state | | LubaLink | Inline text link with default, subtle, and external styles | | LubaMenu | Context menu with icons and destructive item support |
Data Visualization
| Component | What It Does | |-----------|-------------| | LubaBarChart | Vertical/horizontal bar chart with token-based styling | | LubaGroupedBarChart | Multi-series grouped bar chart with auto palette | | LubaLineChart | Line chart with optional area fill and point markers | | LubaMultiLineChart | Multi-series line chart | | LubaPieChart | Pie and donut chart (iOS 17+, uses SectorMark) | | LubaSparkline | Minimal inline trend chart for dashboards | | LubaChartSkeleton | Animated loading placeholder (bar/line styles) | | LubaChartLegend | Custom legend row for chart labels |
Design Tokens
Every value has a name. No magic numbers.
Colors
LubaColors.textPrimary // Main text
LubaColors.textSecondary // Supporting text
LubaColors.accent // Brand color (sage green)
LubaColors.success // Positive states
LubaColors.warning // Caution states
LubaColors.error // Error states
LubaColors.surface // Card backgrounds
LubaColors.background // Page backgroundsAll colors are adaptive — they switch automatically between light and dark mode.
Spacing (4pt base grid)
LubaSpacing.xxs // 2pt
LubaSpacing.xs // 4pt
LubaSpacing.sm // 8pt
LubaSpacing.md // 12pt
LubaSpacing.lg // 16pt
LubaSpacing.xl // 24pt
LubaSpacing.xxl // 32pt
LubaSpacing.xxxl // 48pt
LubaSpacing.huge // 64ptMotion
LubaMotion.pressScale // 0.97 — the sweet spot
LubaMotion.pressAnimation // Quick spring with bounce
LubaMotion.stateAnimation // Smooth state transitions
LubaMotion.micro // Ultra-quick feedback
LubaMotion.gentle // Soft transitions
LubaMotion.disabledOpacity // 0.45Typography
LubaTypography.largeTitle // SF Rounded by default
LubaTypography.title
LubaTypography.headline
LubaTypography.body
LubaTypography.caption
// ... 13 presets totalConfiguration
Control the system globally via SwiftUI's environment:
// Use a preset
ContentView()
.lubaConfig(.accessible) // High contrast, bold text, larger touch targets
ContentView()
.lubaConfig(.minimal) // No animations, no haptics
ContentView()
.lubaConfig(.debug) // Debug outlines and a11y logging
// Customize inline
ContentView()
.lubaConfig { config in
config.hapticsEnabled = false
config.highContrastMode = true
config.useRoundedFont = false
}Components automatically respect these settings via @Environment(\.lubaConfig).
Available Settings
| Property | Default | Description | |----------|---------|-------------| | hapticsEnabled | true | Enable haptic feedback globally | | hapticIntensity | 1.0 | Haptic feedback intensity (0.0 - 1.0) | | animationsEnabled | true | Enable animations globally | | respectReducedMotion | true | Respect system reduced motion setting | | animationSpeed | 1.0 | Animation duration multiplier | | minimumTouchTarget | 44 | Minimum touch target size in points | | useBoldText | false | Bold text for readability | | highContrastMode | false | Increased contrast for semantic colors | | useRoundedFont | true | SF Rounded (true) or SF Pro (false) | | customFontFamily | nil | Custom font family override | | defaultButtonStyle | .primary | Default button style | | defaultCardElevation | .low | Default card elevation | | defaultCornerRadius | 12 | Default corner radius | | showDebugOutlines | false | Show component outlines for debugging | | logA11yWarnings | false | Log accessibility warnings |
Glass Effects
LubaUI includes a glass/frosted primitive that works across iOS versions:
// Apply glass to any view
VStack {
Text("Frosted Content")
}
.padding(LubaSpacing.lg)
.lubaGlass(.regular, tint: LubaColors.accent)
// Three intensity levels
.lubaGlass(.subtle) // Light frosting — toolbars, FABs
.lubaGlass(.regular) // Standard — cards, tab bars
.lubaGlass(.prominent) // Heavy — panels, modals
// Built into components
LubaButton("Action", style: .glass) { }
LubaCard(style: .glass) { content }
LubaToast("Saved", style: .success, useGlass: true)Uses SwiftUI materials on iOS 16-25, ready for native Liquid Glass on iOS 26+. Automatically falls back to a solid surface when Reduce Transparency or High Contrast Mode is active.
MCP Server — AI-Native Design System
LubaUI includes an MCP (Model Context Protocol) server that makes the entire design system queryable by AI assistants like Claude. Instead of reading source files, your AI can look up tokens, validate values, and get component APIs instantly.
One command to set up, works from any project:
claude mcp add lubaui -- npx lubaui-mcp@latestUsing @latest ensures you always get the newest version of the server.
What your AI can do:
- Read the full design system reference in one call (
lubaui://reference/full) - Look up multiple components at once (
lookup_components) - Generate migration mappings from your existing design system (
plan_migration) - Validate spacing and radius values against the token scales
- Get contextual recommendations for what you're building (
suggest_tokens)
See mcp-server/README.md for all 10 tools and 4 resources.
Requirements
- iOS 16.0+ / macOS 13.0+ / watchOS 9.0+ / tvOS 16.0+ / visionOS 1.0+
- Swift 5.9+
- Xcode 15.0+
Contributing
LubaUI welcomes contributions. When adding new components:
- Create component-specific tokens (e.g.,
LubaFooTokens) - Use
LubaMotionfor all animations — never hardcode values - Read
@Environment(\.lubaConfig)for haptics and animations - Use
LubaColorssemantic colors (never raw hex values) - Extract reusable behavior to primitives
- Maintain backwards compatibility
See llms.txt for detailed architecture documentation.
License
LubaUI is available under the MIT License. See LICENSE for details.
<p align="center"> Made with intention by <a href="https://github.com/ermanakar">Erman Akar</a> </p>
Package Metadata
Repository: ermanakar/lubaui
Default branch: main
README: README.md