Skip to content

Getting started

SwiftUI

The web tokens, generated into Swift, plus SwiftUI styles. Early preview.

Install#

Add the package in Xcode (File, Add Package Dependencies) with the repo URL, or in Package.swift. It needs iOS 17, macOS 14, visionOS 1, tvOS 17 or watchOS 10.

Package.swift
.package(url: "https://github.com/tobibytes/junco", branch: "main")

// in your target:
.product(name: "JuncoUI", package: "junco")
Products
ProductWhat it is
JuncoTokensPlain Swift values: colors (light and dark), spacing, radius, type scale, durations, easing. No UI framework, so it also builds on Linux.
JuncoUISwiftUI colors, fonts, motion and styles for buttons, text fields, toggles, badges, cards and fields. Re-exports JuncoTokens.

Tokens#

Every color is a dynamic SwiftUI Color that flips with light and dark mode, named like the CSS variables (--muted-foreground is mutedForeground). Spacing follows the 4pt scale.

import JuncoUI

Text("Revenue")
  .juncoText(JuncoTokens.FontSize.sm)
  .foregroundStyle(Color.junco.mutedForeground)
  .padding(JuncoTokens.Spacing.x(4))        // 16pt, like p-4
  .background(Color.junco.surface)
  .clipShape(RoundedRectangle(cornerRadius: JuncoTokens.Radius.card))

The Swift files are generated from packages/tokens/src/tokens.css by pnpm swift:tokens, and CI fails if they drift. The contrast pairs are checked again in Swift tests, in both modes.

Components#

Junco ships as SwiftUI styles, so you keep using the native controls and get their keyboard, focus and VoiceOver support.

Button("Save") { save() }
  .buttonStyle(.junco)                          // solid, md
Button("Cancel") { }
  .buttonStyle(.junco(.outline, size: .sm))      // solid, outline, soft, ghost, destructive

JuncoField("Email", description: "We never share it.", error: emailError) {
  TextField("you@example.com", text: $email)
    .textFieldStyle(.junco)
}

Toggle("Email alerts", isOn: $alerts)
  .toggleStyle(.junco)

JuncoBadge("Paid", variant: .success, systemImage: "checkmark")

JuncoCard {
  Text("$12,400").juncoText(JuncoTokens.FontSize.xl2, weight: .semibold)
}
What's in the preview
PieceAPI
Button.buttonStyle(.junco(variant, size:, fullWidth:))
Text field.textFieldStyle(.junco)
Field (label, help, error)JuncoField(label, description:, error:, isRequired:)
Switch.toggleStyle(.junco)
BadgeJuncoBadge(text, variant:, systemImage:)
CardJuncoCard { } or .juncoCard()

Fonts#

Font.junco(size, weight:, family:) uses Geist and Geist Mono when your app bundles them (add the .ttf files and list them under UIAppFonts), and the system font otherwise. Either way it scales with Dynamic Type.

Motion#

Animations use the duration and easing tokens. .juncoMotion(.subtle) or .juncoMotion(.off) turns them down for a whole view tree, like data-motion on the web, and the system Reduce Motion setting always turns movement off.

content
  .juncoAnimation(value: isExpanded)
  .juncoMotion(.subtle)