| name | ui-components |
| description | Design system, UI components, Screen, Toolbar, Sheet, and localization reference. |
| user-invocable | false |
UI Components Guide
Design System
Always use the Theme enum for colors and fonts. Never hardcode values.
Colors (Theme.colors.*)
Theme.colors.bgPrimary
Theme.colors.bgSurface1
Theme.colors.bgSurface2
Theme.colors.bgButtonPrimary
Theme.colors.bgButtonSecondary
Theme.colors.bgButtonTertiary
Theme.colors.textPrimary
Theme.colors.textSecondary
Theme.colors.textTertiary
Theme.colors.border
Theme.colors.alertSuccess
Theme.colors.alertError
Theme.colors.alertWarning
Theme.colors.turquoise
Reference: VultisigApp/VultisigApp/Core/DesignSystem/DefaultTheme/ColorSystem.swift
Fonts (Theme.fonts.*)
Theme.fonts.heroDisplay
Theme.fonts.headline
Theme.fonts.title1
Theme.fonts.title2
Theme.fonts.title3
Theme.fonts.bodyLMedium
Theme.fonts.bodyMMedium
Theme.fonts.bodySMedium
Theme.fonts.caption12
Theme.fonts.buttonRegularSemibold
Theme.fonts.priceTitle1
Theme.fonts.priceBodyL
Theme.fonts.priceBodyS
Reference: VultisigApp/VultisigApp/Core/DesignSystem/DefaultTheme/FontSystem.swift
Gradients
LinearGradient.primaryGradient
LinearGradient.primaryGradientLinear
LinearGradient.primaryGradientHorizontal
Buttons
Use PrimaryButton for all buttons. Never create custom button styles.
PrimaryButton(title: "Continue", type: .primary, size: .medium) {
}
PrimaryButton(title: "Send", leadingIcon: "arrow.up", type: .primary, size: .medium) { }
PrimaryButton(title: "Processing", isLoading: true, type: .primary, size: .medium) { }
Button Types:
| Type | Appearance |
|---|
.primary | Blue background (#2155DF) |
.secondary | Dark background with border |
.primarySuccess | Turquoise background (#33E6BF) |
.alert | Red background (#FF5C5C) |
.outline | Transparent with border |
Button Sizes: .medium (full width, 14pt padding), .small (12pt), .mini (6pt), .squared (12pt radius)
Reference: VultisigApp/VultisigApp/Components/Buttons/PrimaryButton/
Text Fields
CommonTextField
General-purpose text input:
@State private var text = ""
@State private var error: String? = nil
@State private var isValid: Bool? = nil
CommonTextField(
text: $text,
label: "Address",
placeholder: "Enter wallet address",
error: $error,
isValid: $isValid
)
Sizes: .normal, .small
Reference: VultisigApp/VultisigApp/Components/TextField/CommonTextField.swift
Form Components
| Component | Location | Purpose |
|---|
AddressTextField | Components/Forms/AddressTextField.swift | Address input with QR/paste accessories |
AmountTextField | Components/Forms/AmountTextField.swift | Amount with percentage + ticker |
PercentageButtonsStack | Components/Forms/PercentageButtonsStack.swift | 25/50/75/100% quick-select |
PercentageSliderView | Components/Forms/PercentageSliderView.swift | Percentage slider input |
MemoTextField | Components/TextFields/MemoTextField.swift | Memo/note input |
SearchTextField | Components/TextField/SearchTextField.swift | Search input |
Screen Component
Standard wrapper for all full-screen views. Always use Screen for screens and suffix the struct name with Screen.
struct MyFeatureScreen: View {
var body: some View {
Screen(title: "myFeature".localized) {
ScrollView(showsIndicators: false) {
LazyVStack(spacing: 14) {
}
}
}
}
}
Parameters:
| Parameter | Type | Default | Description |
|---|
title | String | "" | Navigation bar title |
showNavigationBar | Bool | true | Show/hide nav bar |
edgeInsets | ScreenEdgeInsets | .noInsets | Custom padding |
backgroundType | BackgroundType | .plain | .plain or .gradient |
Default padding: iOS 16pt horizontal / macOS 40pt horizontal, 12pt vertical
Screen(title: "Title", edgeInsets: ScreenEdgeInsets(bottom: 0)) { ... }
Screen(title: "Title", backgroundType: .gradient) { ... }
Reference: VultisigApp/VultisigApp/Components/Screen/Screen.swift
Cross-Platform Toolbar
Unified toolbar for iOS and macOS.
content
.crossPlatformToolbar("Screen Title") {
CustomToolbarItem(placement: .trailing) {
ToolbarButton(image: "gear", action: onSettings)
}
}
Parameters: navigationTitle: String?, ignoresTopEdge: Bool = false, showsBackButton: Bool = true, items: [CustomToolbarItem]
ToolbarButton types: .outline (default), .confirmation (blue), .destructive (red)
ToolbarButton(image: "checkmark", type: .confirmation, action: onConfirm)
ToolbarButton(image: "trash", type: .destructive, action: onDelete)
ToolbarButton(image: "x", iconSize: 16, action: onClose)
Reference: VultisigApp/VultisigApp/Components/Toolbar/CrossPlatformToolbar/
Cross-Platform Sheet
Unified sheet presentation for iOS and macOS.
.crossPlatformSheet(isPresented: $showSheet) {
MySheetContent()
}
.crossPlatformSheet(item: $selectedItem) { item in
ItemDetailSheet(item: item)
}
Sheet content should include:
.presentationDetents([.medium, .large])
.presentationBackground(Theme.colors.bgSurface1)
.presentationDragIndicator(.visible)
Reference: VultisigApp/VultisigApp/Components/Sheet/CrossPlatformSheet.swift
Containers
VStack { ... }
.containerStyle(padding: 16, radius: 12, bgColor: Theme.colors.bgSurface1)
ContainerView { ... }
Reference: VultisigApp/VultisigApp/Components/Layout/ContainerView.swift
Icons
Icon(named: "arrow.up", color: Theme.colors.primaryAccent4, size: 20)
Icon(named: "checkmark", isSystem: true)
Cell Types
Available in Components/Cells/:
| Cell | Purpose |
|---|
VaultCell | Vault display with name, share info |
ChainCell | Chain display |
CoinPickerCell | Coin selection |
TransactionCell | Transaction history row |
UTXOTransactionCell | UTXO-specific transaction |
SettingCell | Settings row |
SettingToggleCell | Settings with toggle |
SettingFAQCell | FAQ setting row |
SettingSelectionCell | Settings with selection |
SwapChainCell / SwapCoinCell | Swap selection |
PeerCell / EmptyPeerCell | Peer device display |
FolderCell / FolderVaultCell | Folder organization |
AddressBookCell | Address book entry |
Banners & Loaders
Banners: Components/Banners/
InfoBannerView - Info banner with icon, title, message
BannerView - Basic banner
ActionBannerView - Banner with action button
OutlinedDisclaimer - Outlined disclaimer box
WarningView - Warning message
Loaders: Components/Loaders/
Loader - Main spinner
CircularProgressIndicator - Circular progress with percentage
LoadingOverlayViewModifier - Blocking loading overlay
Segmented Controls
SegmentedControl(...)
FilledSegmentedControl(...)
Reference: Components/SegmentedControls/
View Modifiers
.showIf(condition)
.if(condition) { view in view.opacity(0.5) }
.unwrap(optionalValue) { view, value in ... }
.plainListItem()
.sheetStyle(padding: 8)
References: Core/Extensions/ViewExtension.swift, Components/List/View+PlainListItem.swift, Components/Sheet/View+SheetStyling.swift
Combining Screen + Toolbar + Sheet
struct MyFeatureScreen: View {
@State private var showDetailSheet = false
@State private var selectedItem: Item?
var body: some View {
Screen(title: "myFeature".localized) {
ScrollView {
LazyVStack(spacing: 14) {
ForEach(items) { item in
ItemRow(item: item)
.onTapGesture { selectedItem = item }
}
}
}
}
.crossPlatformToolbar {
CustomToolbarItem(placement: .trailing) {
ToolbarButton(image: "plus") { showDetailSheet = true }
}
}
.crossPlatformSheet(isPresented: $showDetailSheet) {
AddItemSheet(isPresented: $showDetailSheet)
}
.crossPlatformSheet(item: $selectedItem) { item in
ItemDetailSheet(item: item)
}
}
}
Localization
Supported Languages (7)
| Language | Directory |
|---|
| English (base) | VultisigApp/VultisigApp/Core/Localizables/en.lproj/Localizable.strings |
| German | VultisigApp/VultisigApp/Core/Localizables/de.lproj/Localizable.strings |
| Spanish | VultisigApp/VultisigApp/Core/Localizables/es.lproj/Localizable.strings |
| Croatian | VultisigApp/VultisigApp/Core/Localizables/hr.lproj/Localizable.strings |
| Italian | VultisigApp/VultisigApp/Core/Localizables/it.lproj/Localizable.strings |
| Portuguese | VultisigApp/VultisigApp/Core/Localizables/pt.lproj/Localizable.strings |
| Simplified Chinese | VultisigApp/VultisigApp/Core/Localizables/zh-Hans.lproj/Localizable.strings |
Rules
- Never hardcode user-facing strings - Use
"key".localized
- Add to ALL 7 Localizable.strings files - Every new string must be translated
- camelCase keys - e.g.,
"sendCryptoTitle", "vaultSettings"
- Alphabetical ordering - Keep keys sorted alphabetically within each file
- Use
sort_localizable.py after adding keys:
python3 VultisigApp/scripts/sort_localizable.py VultisigApp/VultisigApp/Core/Localizables/en.lproj/Localizable.strings
Example
In Swift code:
Text("sendCryptoTitle".localized)
In each Localizable.strings:
// en.lproj
"sendCryptoTitle" = "Send Crypto";
// de.lproj
"sendCryptoTitle" = "Krypto senden";
// es.lproj
"sendCryptoTitle" = "Enviar Cripto";
// hr.lproj
"sendCryptoTitle" = "Posalji Kripto";
// it.lproj
"sendCryptoTitle" = "Invia Crypto";
// pt.lproj
"sendCryptoTitle" = "Enviar Cripto";
// zh-Hans.lproj
"sendCryptoTitle" = "发送加密货币";