A custom space / sci-fi UI component library for SwiftUI apps.
- iOS 17+ / macOS 14+ / tvOS 17+ / watchOS 10+
- Swift 5.9+
.package(url: "https://github.com/Yinnotayl/SpaceUI", branch: "main")Add "SpaceUI" as a dependency to your target.
Register SpaceUI fonts once at app launch:
import SpaceUI
@main
struct MyApp: App {
init() {
SpaceFont.register()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}SpaceUI components can inherit a shared style:
VStack {
SpaceCard {
SpaceText("Mission Control")
}
SpaceButton("Deploy") {
deploy()
}
.spaceUIStyle(.primary(role: .confirm))
}
.spaceUIStyle(.primary)Use scoped modifiers when one component should differ without restyling nested SpaceUI elements:
SpaceCard {
SpaceButton("Join") {
join()
}
}
.spaceCardStyle(.glass(role: .confirm, highlighted: true))Available styles:
.primary
.glass
.primary(role: .normal, highlighted: false)
.glass(role: .confirm, highlighted: true)Scoped modifiers:
.spaceCardStyle(.glass)
.spaceListRowStyle(.glass)
.spacePanelStyle(.glass)
.spaceButtonStyle(.primary(role: .confirm))
.spaceTextFieldStyle(.glass)
.spaceChipStyle(.glass(role: .confirm))
.spaceIconButtonStyle(.glass(role: .destructive))You can tune the shared style variables with SpaceUIStyleTokens:
ContentView()
.spaceStyleTokens(
SpaceUIStyleTokens(
normalColor: .cyan,
confirmColor: .mint,
destructiveColor: .red
)
)SpaceUI ships Orbitron and Space Grotesk variants. Display/title/action text defaults to Orbitron; subtitle/body/caption text defaults to Space Grotesk. Space Grotesk styles do not add letter tracking by default.
Semantic text styles:
SpaceLargeDisplay("HYPER THRUST")
SpaceDisplay("HOST")
SpaceDisplay2("Raider MKII")
SpaceTitle("Mission Control")
SpaceTitle2("Subsystems")
SpaceSubtitle("Prepare for high speed chaos")
SpaceText("Hull integrity nominal")
SpaceCaption("SERVERS")Modifier forms are available too:
Text("JOIN").spaceDisplay()
Text("Tap anywhere").spaceSubtitle(.orbitronMedium, color: .white)
Text("42").spaceTextStyle(26, font: .orbitronMedium)Compatibility aliases remain available:
.orbitron_medium
.din_alternate // deprecated alias to Space Grotesk RegularContentView()
.spaceBackground()
ContentView()
.spaceAnimatedBackground()SpaceBackground is the existing static game background. SpaceAnimatedBackground layers scrolling stars and fog over it.
SpaceCard(title: "Warp Drive", subtitle: "Nominal")
.spaceUIStyle(.primary(role: .confirm))
SpaceButton("Launch") {
launch()
}
.spaceUIStyle(.primary(role: .confirm, highlighted: true))
SpaceButtonTwoStep("Delete") {
delete()
}
.spaceUIStyle(.primary(role: .destructive))
SpaceIconButton("xmark", accessibilityLabel: "Close") {
close()
}
.spaceIconButtonStyle(.glass(role: .destructive))SpaceChip("AI", isOn: $aiEnabled, icon: "sparkles")
.spaceUIStyle(.glass(role: .confirm))
SpaceChip(
isOn: $serverPublic,
onText: "PUBLIC",
offText: "PRIVATE",
onIcon: "antenna.radiowaves.left.and.right",
offIcon: "lock"
)
.spaceChipStyle(.glass(role: .normal))SpaceChip maps isOn to the style highlight state, so active chips get the highlighted outline/glow for the inherited or scoped role.
SpaceTextField("Callsign", text: $callsign, placeholder: "Enter callsign")
SpaceSection("Servers") {
SpaceListRow(title: "Yijue's iPad", status: "JOIN") {
join()
}
.spaceUIStyle(.glass(role: .confirm))
}SpaceListRow supports selected and disabled states for multiplayer-selector style rows:
SpaceListRow(
title: "Server",
status: "CONNECTING",
selected: true,
disabled: true
)
.spaceListRowStyle(.glass)@State private var focusedSide: SpaceSplitSide = .right
SpaceSplitView(focusedSide: $focusedSide, dimming: true) {
hostControls
} rightContent: {
joinControls
}When dimming is true, tapping the unfocused side updates focusedSide. When dimming is false, only the developer-controlled binding changes focus.
SpacePulseText("Tap anywhere to begin")SpaceUIRole controls role colors for borders, glows, and status accents:
.normal
.confirm
.destructiveCLANG_MODULE_CACHE_PATH=/private/tmp/spaceui_module_cache swift test --disable-sandboxSpaceUI is available under the AGPL-3.0 license. See LICENSE for details.