Skip to main content
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:
SuffixApplies 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.

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
    }
}

Build docs developers (and LLMs) love