Without a Theming Engine, every design change in a white-label app requires a rebuild and release. One of our clients was spending 72 man-hours per month updating themes for 15 brands. After implementing a Theming Engine, that task shrank to one hour: just upload a new JSON config to the server. Result: 98% time savings and full flexibility. We have 6+ years of experience in white-label development and have delivered over 30 projects with dynamic theming. If you need to flexibly manage the appearance of dozens of brands, contact us — we will propose a solution.
How Does a Theming Engine Improve White-Label Apps?
A Theming Engine is a system that manages visual parameters of an app through design tokens. All colors, fonts, sizes, corner radii, and shadows are externalized into a JSON config that is loaded at startup or switched at runtime. This enables runtime theme switching without rebuild, supports hundreds of brands in a single build, and adapts the interface to user preferences. A single configuration for all brands simplifies maintenance.
Config Structure (Example for Tenant "Brand B")
{
"tenant": "brand_b",
"version": "2",
"colors": {
"primary": "#1A73E8",
"primary_variant": "#1557B0",
"secondary": "#FB8C00",
"background": "#FFFFFF",
"surface": "#F5F5F5",
"error": "#B00020",
"on_primary": "#FFFFFF",
"on_secondary": "#000000"
},
"typography": {
"font_family": "Inter",
"scale_factor": 1.0
},
"shape": {
"card_corner_radius": 12,
"button_corner_radius": 8,
"input_corner_radius": 4
},
"assets": {
"logo_url": "https://cdn.brand-b.com/logo.png",
"splash_bg_color": "#1A73E8"
}
}
Static vs Dynamic Approach Comparison
| Criteria |
Static Theme (xcconfig/flavors) |
Dynamic Theme (Theming Engine) |
| Theme change without release |
No |
Yes |
| Number of tenants |
Up to 10 |
Unlimited |
| Runtime theme switching support |
No |
Yes |
| Implementation complexity |
Low |
Medium |
| Design flexibility |
Limited |
High |
Estimated Implementation Time by Platform
| Platform |
New Project |
Refactoring Existing |
| iOS (SwiftUI) |
2 weeks |
3–5 weeks |
| Android (Compose) |
2 weeks |
3–5 weeks |
| Flutter |
3 weeks |
4–6 weeks |
| React Native |
3 weeks |
4–6 weeks |
How to Implement Dynamic Theme in Jetpack Compose?
Jetpack Compose makes dynamic theming significantly easier than XML: MaterialTheme accepts ColorScheme and Typography as parameters and applies them to the entire component tree.
// Loading and parsing the theme in Compose
@Composable
fun TenantThemedApp(theme: TenantTheme, content: @Composable () -> Unit) {
val colorScheme = lightColorScheme(
primary = Color(android.graphics.Color.parseColor(theme.colors.primary)),
primaryContainer = Color(android.graphics.Color.parseColor(theme.colors.primaryVariant)),
secondary = Color(android.graphics.Color.parseColor(theme.colors.secondary)),
background = Color(android.graphics.Color.parseColor(theme.colors.background)),
surface = Color(android.graphics.Color.parseColor(theme.colors.surface)),
error = Color(android.graphics.Color.parseColor(theme.colors.error))
)
val shapes = Shapes(
small = RoundedCornerShape(theme.shapes.inputCornerRadius.dp),
medium = RoundedCornerShape(theme.shapes.cardCornerRadius.dp),
large = RoundedCornerShape(theme.shapes.buttonCornerRadius.dp)
)
MaterialTheme(colorScheme = colorScheme, shapes = shapes, content = content)
}
// Usage in Activity
setContent {
val theme by themeViewModel.tenantTheme.collectAsState()
TenantThemedApp(theme = theme) {
AppNavHost()
}
}
When tenantTheme changes in the ViewModel, the entire UI redraws automatically. Runtime font loading is done via the Downloadable Fonts API; fonts are cached after the first load, eliminating startup delays. In practice, a theme switch takes less than 2 seconds, whereas a static release takes 24 hours.
Flutter and React Native
Flutter: ThemeData in MaterialApp is parameterized similarly to Compose. For full control, use InheritedWidget or a Riverpod provider with the theme object. Runtime font loading via the FontLoader API.
React Native: ThemeContext via React Context API; StyleSheet.create is called with tokens from the context. Hot-reloading the theme without restart is achieved via useContext(ThemeContext) in components.
Why is the Stale-While-Revalidate Pattern Important for Theme Loading?
Stale-while-revalidate (described in MDN documentation) is a caching strategy that guarantees data freshness without delays. Show the cached theme immediately, update in the background. This eliminates waiting at startup and prevents broken UI on network errors.
class ThemeStore: ObservableObject {
@Published var currentTheme: TenantTheme = .default
func loadTheme(tenantId: String) async {
do {
if let cached = ThemeCache.load(tenantId: tenantId) {
await MainActor.run { currentTheme = cached }
}
let dto = try await api.fetchTheme(tenantId: tenantId)
let theme = TenantTheme(from: dto)
ThemeCache.save(theme, tenantId: tenantId)
await MainActor.run { currentTheme = theme }
} catch {
// Fallback to default theme, do not crash
}
}
}
Standard loading without this pattern would result in an empty screen for 200–300 ms. Compare: a static theme takes 24 hours for release, a dynamic one switches in 2 seconds — a 43,200x difference.
How Does Theme Versioning Prevent Broken UI?
When the contract is updated (a new token is added), older cached themes may lack that field. If the theme version is below the minimum supported version, we use a bundled default theme. This protects against displaying broken UI.
Process of Work
- UI audit: inventory all colors, fonts, radii in the app, identify hardcoded values.
- Token schema design together with the designer: which parameters differ between brands.
- Implement ThemeProvider, environment-based application, loading from API.
- Refactor components: replace hardcoded values with tokens, cover with visual tests (Paparazzi for Android, SwiftUI Previews for iOS).
- Test runtime theme switching: all components redraw correctly, fonts load without flickering.
Timeline Estimates
A Theming Engine for a new app from scratch (Compose or SwiftUI) — 2–3 weeks. Refactoring an existing app with hardcoded colors — depends on codebase size, typically 3–6 weeks. Cost is calculated individually.
Theming Engine Implementation Checklist
- UI audit: inventory all colors, fonts, radii
- Design token schema with designer
- Implement ThemeProvider and theme loader
- Refactor components: replace hardcoded values with tokens
- Configure caching and error handling
- Test theme switching: all components redraw correctly
- Document adding new tenants
Ready to implement a Theming Engine in your white-label app? Get expert consultation — we will analyze your UI and propose the optimal solution.
White-Label Mobile Apps: SDK, Modular Architecture, and Multi-tenancy
In our practice, white-label development is not just "repainting an app." It is an architectural decision that must be made from the start. We have repeatedly encountered attempts to refactor a monolithic app into white-label post-factum — this is one of the most expensive refactorings in mobile development. Over 5 years of work, we have implemented 20+ white-label projects for iOS, Android, and cross-platform stacks. We guarantee: a proper modular architecture reduces the time to onboard a new client to 2 days, not 2 months. A white-label product is not just a logo change, but a full-fledged multi-tenant system. Get a consultation on white-label architecture — we will help you choose the right approach for your project.
What does "proper" modular architecture mean for white-label?
The key principle: no business logic module should know about a specific client. Configuration, colors, texts, feature flags — everything comes from outside via dependency injection, not hardcoded.
On iOS, the correct structure: Swift Packages for each domain (AuthKit, PaymentsKit, ProfileKit), a separate AppKit for the entry point, and BrandKit — a package with the specific client's theme. The main app is a thin wrapper that assembles them together.
// Wrong
class PaymentViewController: UIViewController {
let primaryColor = UIColor(hex: "#FF5722") // hardcoded client A
}
// Correct
class PaymentViewController: UIViewController {
let theme: AppTheme // injected at build time
}
On Android, the equivalent is Gradle multi-module with productFlavors. Each flavor builds the app for a specific client: substitutes google-services.json, theme, resources. One repository, multiple artifacts.
Theming: beyond colors and fonts
A superficial white-label — change colors and logo. This takes a day. Real customization — when a client can disable a module, change the order of onboarding screens, use their own payment provider.
In React Native for deep theming: ThemeProvider (React Context) at the top level, design tokens (colors.primary, spacing.md) via theme object, components get values only through tokens. For Flutter: ThemeData + ThemeExtension for custom tokens beyond Material Design.
Runtime theming (loading theme from the server) is a separate level of complexity. On iOS, UIAppearance proxy + manual update for existing views is unavoidable; SwiftUI with Environment and @EnvironmentObject for theme simplifies this significantly. According to App Store Review Guidelines Section 5.1.1, user data, including theme settings, must be processed with explicit consent.
Why multi-tenancy is critical for white-label
If multiple white-label apps use a shared backend, tenant identification at the API level is required. Option 1: X-Tenant-ID header in each request. Option 2: separate subdomains (clienta.api.example.com). Option 3: tenant from JWT token after authentication.
At the mobile app level: tenant ID is either hardcoded into the configuration at build time (via xcconfig / gradle.properties) or determined dynamically (by bundle ID or Remote Config). Dynamic determination is needed if one binary serves multiple clients — a rare but real enterprise case.
Savings from a multi-tenant architecture compared to dedicated backends are up to 60% on operational costs with 5+ clients.
How to properly design a white-label architecture?
A step-by-step approach we use:
-
Analysis — define domains that will be common to all clients and customization points.
-
Stack selection — for iOS: Swift Packages + XCFramework; for Android: Gradle multi-module + AAR; for cross-platform: Flutter/Dart package or React Native library.
-
Configuration design — JSON schema for brand (colors, fonts, feature flags, links), validation on the client.
-
Module implementation — each module publishes a protocol/interface; concrete implementation is injected via DI container (Swinject, Dagger Hilt).
-
Build automation — Fastlane + CI pipeline with CLIENT_ID parameter.
-
Testing — UI tests for each brand (screenshot testing with Percy or Firebase Test Lab).
-
Deployment — parallel upload to App Store Connect / Google Play Console via distributed builds.
SDK: when white-label is a library
If the product is embedded into other apps — it's an SDK, not a white-label app. Requirements are fundamentally different.
iOS SDK via Swift Package Manager: Package.swift describes the product, target, dependencies. Published via git tag. Critical: do not pull transitive dependencies unnecessarily — each SDK dependency potentially conflicts with the host app's dependencies.
Android SDK via Maven (Artifactory or GitHub Packages): AAR artifact with POM metadata. api() vs implementation() in gradle — export only what the client needs in api(). Everything internal — implementation().
SDK versioning via Semantic Versioning is mandatory. Breaking changes in minor version is death for a B2B product.
Typical mistakes when developing a white-label SDK
- Lack of backward compatibility at the API level (breaking changes in patch version).
- Using internal types in public methods (violating encapsulation).
- Tight coupling to a specific DI library of the host app.
- Insufficient setup documentation (how to sign XCFramework, how to add AAR to Gradle).
How to automate builds for a dozen brands
For 5+ white-label clients, manual builds are inefficient. Fastlane with lanes per client and shared methods is the baseline. A more mature solution: a parameterized CI pipeline where you pass CLIENT_ID and get a built IPA/APK/AAB for a specific brand.
Codemagic supports environment variables per workflow — convenient for multi-brand builds without a complex Fastfile.
Comparison of white-label approaches:
| Approach |
Implementation complexity |
Customization flexibility |
Time to onboard a new brand |
| Fork repository |
Low (2–3 days) |
Minimal (code copy) |
1–2 days |
| Feature flags + configs |
Medium (2–4 weeks) |
Medium (colors, texts, modules) |
2–4 hours |
| Multi-module / productFlavors |
High (1–2 months) |
High (any logic) |
30 minutes |
| SDK + white-label wrapper |
Very high (2+ months) |
Maximum (embedding into other apps) |
1–2 days |
White-label based on productFlavors is 3–5 times faster when adding a new client than the fork approach, and reduces the risk of codebase divergence. Cost savings on maintaining 10 brands reach 70% compared to code copying.
What is included in the work
We provide white-label development turnkey:
-
Architectural design — stack selection, modular breakdown, multi-tenancy scheme.
-
Core functionality — authorization, profile, payment module (StoreKit 2 / Billing 6), push notifications (APNs / FCM), deep linking (Universal Links / App Links).
-
Branding tools — theming, feature flags, screen configuration.
-
Documentation — module description, build instructions, client guide.
-
Access — repository, CI/CD, App Store Connect / Google Play Console.
-
Training — 2–4 hours of demo for the client's team.
-
Support — 1 month of warranty support after release.
Approximate timelines
| Task type |
Timeline |
| Refactoring a monolith into white-label architecture |
2–3 months |
| New white-label app from scratch (iOS / Android) |
3–4 months |
| Mobile SDK development |
from 6 weeks (simple), from 3 months (full-featured) |
Cost is calculated individually — depends on the number of modules, platforms, and depth of customization. Order a free analysis of your architecture — we will estimate the workload in 2 days. Contact us to discuss your white-label project — we guarantee compliance with App Store Review Guidelines and Google Play policies.