ThemeManager.shared is the single entry point for all theme customization. You can pin the app to a specific palette version, override individual colors by style and variant, supply a fully custom palette implementation, or use an .nss stylesheet file for tooling-driven overrides.
All calls to ThemeManager.shared should happen in AppDelegate.application(_:didFinishLaunchingWithOptions:), before any UI loads. Most Fiori components resolve colors once at render time and do not observe the manager dynamically.
Pin the palette version
By default, the manager uses .latest (currently v8). Pin to a specific version if you want your app’s styling to remain stable across SDK updates.
// PaletteVersion maps to SAP Fiori SDK releases:
// .v3_x — SDK 3.0 SP01
// .v3_2 — SDK 3.0 SP02 (Fiori 3 styling)
// .v4 — SDK 5.0 (introduced dark mode)
// .v5 — SDK 6.0 (introduced elevated & contrast colors)
// .v6 — SDK 7.0 (Fiori Next styling)
// .v7 — SDK 8.0 (Fiori Next styling)
// .v8 — SDK 9.1 (Fiori Next styling, current latest)
ThemeManager.shared.setPaletteVersion(.v7)
watchOS uses a separate v1 palette. iOS and visionOS use v3_x through v8.
Override a single color
By SwiftUI Color
Override a color style for the .light background scheme (i.e., the dark color variant, used when the system is in light mode):
// Override .positive for the light background scheme only
ThemeManager.shared.setColor(.systemGreen, for: .positiveLabel)
Override for a specific ColorVariant to target dark mode, elevated surfaces, or high contrast:
// Override .tintColor in light mode (dark variant of the color)
ThemeManager.shared.setColor(.blue, for: .tintColor, variant: .dark)
// Override .tintColor in dark mode (light variant of the color)
ThemeManager.shared.setColor(Color(hex: "4DB1FF")!, for: .tintColor, variant: .light)
By hex string
When you have a brand hex value, use setHexColor to avoid constructing a Color manually:
// Override .positive for the light background scheme
ThemeManager.shared.setHexColor("1b931d", for: .positiveLabel)
// Override for a specific variant
ThemeManager.shared.setHexColor("0A6ED1", for: .tintColor, variant: .dark)
ThemeManager.shared.setHexColor("4DB1FF", for: .tintColor, variant: .light)
Hex strings may be 3-character (RGB), 6-character (RRGGBB), or 8-character (RRGGBBAA) format. The alpha component is trailing.
Complete custom palette
For advanced use cases where you need to replace the entire palette — for example, a fully branded white-label app — implement the Palette protocol and supply it:
ThemeManager.shared.setPalette(MyBrandPalette())
Creating a complete custom palette is uncommon. It is more practical to call setColor or setHexColor for the specific styles that differ from the SAP defaults.
Reset all overrides
reset() clears every override applied through setColor, setHexColor, and .nss stylesheet loading. It does not change the active palette version.
ThemeManager.shared.reset()
If you also loaded a stylesheet via StyleSheetSettings, call its reset too:
StyleSheetSettings.reset() // also calls ThemeManager.shared.reset() internally
Stylesheet overrides (.nss)
The .nss (NUI Style Sheet) mechanism lets you override colors through a plain-text key–value file, which is convenient for tooling, design handoff, or A/B testing without recompiling.
Format
Keys are ColorStyle raw-value strings, optionally suffixed with a background scheme:
@primaryLabel:#223548;
@primaryLabel_darkBackground:#F5F6F7;
@tintColor:#0070F2;
@tintColorDark:#0070F2;
@tintColorLight:#4DB1FF;
The suffix conventions are:
| Suffix | Applies to |
|---|
| (none) | .dark variant (light-background foreground) |
_darkBackground | .light variant (dark-background foreground) |
_elevatedDarkBackground | .elevatedLight variant |
_elevatedLightBackground | .elevatedDark variant |
Load from a URL
if let url = Bundle.main.url(forResource: "theme", withExtension: "nss") {
try? StyleSheetSettings.loadStylesheetByURL(url: url)
}
Load from a string
let overrides = """
@primaryLabel:#223548;
@primaryLabel_darkBackground:#F5F6F7;
@tintColor:#0070F2;
"""
try? StyleSheetSettings.loadStylesheetByString(content: overrides)
Stylesheet overrides take precedence over the base palette but are overridden by ThemeManager.shared.setColor(...) / setHexColor(...) calls. The priority order is: developer overrides (setColor) > stylesheet overrides > palette defaults.
Recommended AppDelegate setup
The following pattern covers the most common customization needs in a single location:
import FioriThemeManager
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 1. Register SAP 72 fonts
Font.registerFioriFonts()
// 2. Pin the palette to a known version
ThemeManager.shared.setPaletteVersion(.v8)
// 3. Override brand-specific colors
ThemeManager.shared.setHexColor("0070F2", for: .tintColor, variant: .dark)
ThemeManager.shared.setHexColor("4DB1FF", for: .tintColor, variant: .light)
ThemeManager.shared.setColor(.darkGray, for: .secondaryLabel, variant: .dark)
// 4. (Optional) Load an .nss stylesheet for design-token overrides
if let url = Bundle.main.url(forResource: "brand", withExtension: "nss") {
try? StyleSheetSettings.loadStylesheetByURL(url: url)
}
return true
}
}