Design Token System
How Figma design tokens flow from JSON files through ConfigParser to runtime colors, sizes, and typography in every customer app.
Overview
The design token system is the bridge between design and code. Designers define tokens in Figma (colors, sizes, typography), export them as JSON files, and customer apps bundle those files. At runtime, the platform resolves token references into concrete UIColor and CGFloat values that style every screen.
The system has two sides:
- Bricks (Core) defines what tokens exist via
ThemeTokens, a struct with ~180 static constants representing every color, size, and radius used across the platform. Bricks screens reference tokens by name without knowing the actual values. - Customer apps define what tokens mean by providing JSON files with the real hex colors and pixel values, plus a
ConfigParserthat resolves token references at runtime.
This separation means Bricks can build screens against semantic names like "theme/navigation/bottom/highlight", and each customer app maps that to its own brand color.
Figma Design
|
v
JSON Export (tokens.json, colors.json, sizes.json)
|
v
App Bundle (Resources/ConfigTokens/)
|
v
ConfigParser resolves at runtime
|
+---> UIColor (for color tokens)
+---> CGFloat (for size tokens)
|
v
UIConfigurator applies to UIKit componentsDual Theming Approach
The platform uses two parallel systems for resolving visual properties, and both are active simultaneously:
-
Token-based (dynamic):
ConfigParser.colorFromToken(ThemeTokens.someToken)resolves through the JSON pipeline. Changing the JSON files changes the theme without code changes. Used primarily for semantic and contextual colors (navigation backgrounds, card overlays, button states, pill colors). -
Hardcoded (static):
AppTheming.ColourType.accent.colorreturns a hardcoded hex value directly. Used for fundamental palette access where the indirection of the JSON pipeline is unnecessary (basic error/success/warning colors, black/white).
Both approaches coexist throughout the codebase. For example, a cell theme config might use a token-resolved background color alongside a hardcoded AppTheming.ColourType for its text color.
Current Limitations
- No dark mode support. The JSON files contain a single set of values. Supporting dark mode would require either duplicate JSON files or conditional logic in
ConfigParser. - ConfigParser lives in the customer app, not in Bricks. Each customer app has its own copy of the parser. This is by design (customers may customize resolution logic) but means the parsing algorithm is duplicated.
Token Files
The JSON files follow the W3C Design Token specification, which is the standard export format from Figma Variables. Each entry uses $type and $value fields.
Customer apps include up to four JSON files in the app bundle. Three are required, one is optional.
tokens.json (Semantic Layer)
The routing layer. Maps semantic token paths to references in the color or size files. Each entry has a $type (either "color" or "number") and a $value that points to the actual value.
{
"theme": {
"navigation": {
"bottom": {
"highlight": {
"$type": "color",
"$value": "{Accent.accent-600}"
}
}
}
},
"theme-button-height": {
"$type": "number",
"$value": "{button-height}"
}
}Token values use brace notation for indirection: "{Accent.accent-600}" means "look up Accent > accent-600 in colors.json." Values can also reference other tokens within tokens.json itself, enabling aliases.
colors.json (Color Palette)
A nested dictionary of color families, each containing a scale of shades. Values are hex strings or rgba() strings.
{
"Accent": {
"accent-600": {
"$type": "color",
"$value": "#017cff"
},
"accent-400": {
"$type": "color",
"$value": "#77a9f9"
}
},
"Light": {
"light-50": {
"$type": "color",
"$value": "rgba(255, 255, 255, 0.2000)"
},
"light-600": {
"$type": "color",
"$value": "#ffffff"
}
}
}Color families typically include shades from 50 (lightest) through 900 (darkest). Both #hex and rgba(r, g, b, a) formats are supported.
sizes.json (Size Values)
Numeric values for paddings, border sizes, radii, and component dimensions.
{
"paddings": {
"padding-2xl-20": {
"$type": "number",
"$value": 20
},
"padding-md-8": {
"$type": "number",
"$value": 8
}
},
"border-size": {
"border-size-xs-1": {
"$type": "number",
"$value": 1
}
},
"button-height": {
"$type": "number",
"$value": 46
}
}textStyles.json (Typography, Optional)
Only needed if the customer app uses the JSON-based typography approach. Contains an array of named text styles with font family, weight, size, and case information.
{
"fileName": "textStyles",
"textStyles": [
{
"name": "headline",
"fontFamily": "CustomFont",
"fontWeight": "Bold",
"fontSize": 16,
"textCase": "NONE"
},
{
"name": "body",
"fontFamily": "CustomFont",
"fontWeight": "Regular",
"fontSize": 14,
"textCase": "NONE"
}
]
}The textCase field supports "UPPER", "TITLE", and "NONE".
ConfigParser
ConfigParser is a customer-app class with static methods that resolve token strings into UIColor and CGFloat values. It loads the three JSON files once (as static properties) and traverses them at runtime.
Color Resolution
Two methods handle color tokens:
colorFromToken(_:) resolves full path tokens like "theme/navigation/bottom/highlight":
- Splits the token by
"/"to get keys:["theme", "navigation", "bottom", "highlight"] - Traverses
tokens.jsonusing those keys to find the leaf entry - Reads
$type(must be"color") and$value - If
$valueis already a hex or rgba string, returns the color directly - If
$valueuses brace notation (e.g.,"{Accent.accent-600}"), strips braces and splits by"."to get color keys - Traverses
colors.jsonusing those keys to find the$valuehex/rgba string - Returns the
UIColor
colorFromShortToken(_:) resolves brace-wrapped references like "{Accent.accent-400}" directly against colors.json, bypassing tokens.json.
Size Resolution
sizeFromToken(_:) follows the same pattern as colorFromToken but expects $type: "number" and returns a CGFloat.
Recursive Resolution
Token values in tokens.json can reference other tokens within the same file. ConfigParser handles this recursively: if looking up keys in colors.json or sizes.json fails, it falls back to tokens.json to resolve the intermediate reference.
To prevent infinite loops, a depth counter tracks each token's recursion level with a maximum depth of 3. If exceeded, the parser logs an error and returns a fallback value (.clear for colors, 0 for sizes).
Resolution Diagram
colorFromToken("theme/navigation/bottom/highlight")
|
|-- Split by "/" --> keys: ["theme", "navigation", "bottom", "highlight"]
|
|-- Traverse tokens.json with keys
| --> { "$type": "color", "$value": "{Accent.accent-600}" }
|
|-- Strip braces, split by "."
| --> color keys: ["Accent", "accent-600"]
|
|-- Traverse colors.json with color keys
| --> { "$type": "color", "$value": "#017cff" }
|
|-- UIColor(hexOrRgba: "#017cff")
|
--> UIColor (blue)Typography
Youll supports two approaches to typography. Both must conform to TypographyTypeProtocol, a protocol defined in Core that requires:
public protocol TypographyTypeProtocol {
var font: UIFont { get }
var suiFont: Font { get }
var kern: CGFloat { get }
var uppercased: Bool? { get }
var paragraphStyle: NSMutableParagraphStyle { get }
}Code-Based Typography (Recommended)
The customer app defines an AppTheming.TypographyType enum whose cases directly return UIFont instances through a FontBook helper. This approach keeps typography definitions in Swift alongside other theming code.
// FontBook maps font family names to UIFont
enum FontBook {
case text
func font(ofSize size: CGFloat, style: Style) -> UIFont {
let name = fontName + "-" + style.rawValue
guard let font = UIFont(name: name, size: size) else {
return UIFont.systemFont(ofSize: size, weight: style.weight)
}
return font
}
private var fontName: String {
switch self {
case .text: return "YourCustomFont"
}
}
}
// TypographyType uses FontBook for each scale step
public enum TypographyType: String, TypographyTypeProtocol {
case large_title, title_1, title_2, title_3
case headline, body, callout, subhead
case footnote, caption_1, caption_2
public var font: UIFont {
switch self {
case .large_title: return FontBook.text.font(ofSize: 27, style: .bold)
case .headline: return FontBook.text.font(ofSize: 16, style: .bold)
case .body: return FontBook.text.font(ofSize: 14, style: .regular)
// ... remaining cases
}
}
}FontBook falls back to UIFont.systemFont if the custom font is not found, ensuring the app never crashes on missing fonts.
The suiFont property supports Dynamic Type when Settings.useDynamicFontSizes is enabled, mapping each typography type to an Apple text style for accessibility scaling.
JSON-Based Typography (Alternative)
TypographyParser loads a textStyles.json file and matches style names against TypographyType.rawValue. This approach is useful when designers want to control typography from Figma without code changes.
// TypographyParser resolves fonts from JSON
TypographyParser.getFont(for: .headline) // Returns UIFont based on textStyles.json
TypographyParser.isUppercased(for: .headline) // Returns Bool based on textCaseThe parser iterates through text styles looking for a name match, then constructs a UIFont from the fontFamily, fontWeight, and fontSize fields.
AppTheming
AppTheming is a struct defined in the customer app that centralizes all visual constants. It contains four enums:
ColourType
Named color palette with hardcoded values. Used for colors that don't come from tokens (static brand colors, fallback colors, utility colors).
public enum ColourType: String {
case main, second, third, fourth
case dark, accent, light
case error, warning, success
case black, white
var color: UIColor {
switch self {
case .main: return UIColor.hex(0x0E0E13)
case .accent: return UIColor.hex(0x017CFF)
case .error: return UIColor.hex(0xC0373E)
// ... remaining cases
}
}
}TypographyType
The typography scale, described in the Typography section above.
CornerRadiusType
A scale of corner radius values from 0 to 100 points:
public enum CornerRadiusType: String {
case radius_1 // 0 (no radius)
case radius_4 // 8
case radius_5 // 12
case radius_6 // 16
case radius_8 // 24
case radius_100 // 100 (pill shape)
var cornerRadius: CGFloat { /* ... */ }
}ThemeBorderType
A scale of border widths from 0 to 4 points:
public enum ThemeBorderType: String {
case none // 0
case small // 1
case medium // 1.5
case large // 2
case extraLarge // 2.5
case superLager // 3
case thickest // 4
var borderWidth: CGFloat { /* ... */ }
}ThemeTokens Registry
ThemeTokens is a struct in Core (the Bricks shared codebase) that holds ~180 static string constants for every token used across the platform. It serves as the single registry of token paths.
public struct ThemeTokens {
// Palette references (brace notation, resolve directly in colors.json)
public static let main = "{Main.main-300}"
public static let accent = "{Accent.accent-400}"
// View element tokens (path notation, resolve through tokens.json)
public static let themeNavigationBottomHighlight = "theme/navigation/bottom/highlight"
public static let themeGeneralOnBackground = "theme/general/on-background"
public static let themeButtonsPrimaryActiveSurface = "theme/buttons/primary/active/surface"
// Size tokens (path notation, resolve through tokens.json to sizes.json)
public static let themeButtonsRadius = "theme/buttons/radius"
public static let themeCardsRadiusGeneralSmall = "theme/cards/radius/general-small"
}Tokens are organized by category:
- Palette colors use brace notation and resolve directly in
colors.json - View element colors use path notation and resolve through
tokens.jsontocolors.json - Sizes use path notation and resolve through
tokens.jsontosizes.json
Bricks screens call ConfigParser.colorFromToken(ThemeTokens.themeNavigationBottomHighlight) or ConfigParser.colorFromShortToken(ThemeTokens.accent) to get the customer's actual colors without knowing what those colors are.
UIConfig Protocol
UIConfig is the protocol defined in Core that every customer app must implement. It defines how Bricks applies theming to shared UI components.
public protocol UIConfig {
func applyButtonStyle(_ style: ButtonStyleProtocol, title: String, to button: YoullTextButton)
func configureAuthButton(_ button: UIButton, title: String?, forOption option: AuthOption, styleType: AuthConnectionWithButtonType)
func generateGradientLayer() -> CAGradientLayer
func backgroundColorForToastStyle(_ style: ToastStyle) -> UIColor
func applyStyle(navigationController: UINavigationController)
func applyStyle(tabBar: UITabBar)
func applyPopupBarStyle(viewController: UIViewController)
var cellsThemingLayoutManager: CellsThemingLayoutManagerProtocol { get }
}The customer app creates a concrete UIConfigurator class (conforming to UIConfig) that uses ConfigParser and AppTheming to implement each method. For example, applyStyle(tabBar:) uses ConfigParser.colorFromToken(ThemeTokens.themeNavigationBottomHighlight) to set the selected tab color.
Injection
The UIConfigurator is injected at app startup in AppDelegate:
Client.setupUIConfigurator(with: UIConfigurator.shared)Client.setupUIConfigurator(with:) stores the UIConfig instance in a module-level variable. Bricks modules access it through a computed property called UIConfigurator that fatally errors if setup was not called.
Cell Theming
Collection view cells across the platform are themed through CellsThemingLayoutManager, a class in Core that maps cell styles to themed layout views.
CellsThemingLayoutManagerProtocol
public protocol CellsThemingLayoutManagerProtocol {
@MainActor
func getLayout(style: CellStyleType, cellThemeConfig: CellThemeConfig) -> UIView
}Given a cell style (e.g., .smallVertical, .largeHorizontal, .featured) and a theme config, the manager returns a fully styled UIView.
Cell Theme Configs
Each cell style has a corresponding config type inheriting from CellThemeConfig. There are approximately 15 config types covering all card layouts in the platform:
SmallVerticalCellThemeConfig,SmallHorizontalCellThemeConfig,SmallCenteredCellThemeConfigLargeVerticalCellThemeConfig,LargeHorizontalCellThemeConfigFeaturedCellThemeConfig,ClassicCardCellThemeConfig,ProgramCardThemeConfigProgressCellThemeConfig,TextCellThemeConfig,TagCellThemeConfigBannerCellThemeConfig,DailyQuoteCellThemeConfigTuyaHardwareConfig,TimerSessionCellThemeConfig
The customer app also implements a CollectionViewCellsThemingManager (conforming to CollectionViewCellsThemingManagerProtocol from Core) that maps each CellStyleType to a concrete CellThemeConfig subclass. This is where the customer decides which colors, fonts, and sizes each cell layout uses, calling ConfigParser for token-resolved values and AppTheming for hardcoded palette values.
The customer app creates both the CellsThemingLayoutManager and the CollectionViewCellsThemingManager instances and injects them into UIConfigurator.
Setting Up Theming for a New Customer App
-
Export tokens from Figma. Use the Figma Tokens plugin (or equivalent) to export
tokens.json,colors.json, andsizes.json. Place them inResources/ConfigTokens/in the app bundle. See the Design Token Workflow guide for the full export process (coming soon). -
Register custom fonts. Add font files (
.ttfor.otf) to the app bundle and list them inInfo.plistunderUIAppFonts. Create aFontBookenum that maps to your font family names. -
Implement
AppTheming. Create a struct with four enums:ColourTypewith your brand's named colorsTypographyTypeconforming toTypographyTypeProtocol, usingFontBookfor font resolutionCornerRadiusTypewith your radius scaleThemeBorderTypewith your border width scale
-
Implement
ConfigParser. Create a class with static methodscolorFromToken(_:),colorFromShortToken(_:), andsizeFromToken(_:)that load your JSON files and resolve token paths. The implementation follows the resolution algorithm described above. -
Implement
UIConfigurator. Create a class conforming toUIConfigthat usesConfigParserandAppThemingto style buttons, tab bars, navigation bars, toast messages, popup bars, and auth buttons. Include aCellsThemingLayoutManagerinstance. -
Inject at startup. In
AppDelegate.didFinishLaunchingWithOptions, call:Client.setupUIConfigurator(with: UIConfigurator.shared)
For Agents
Key Files
Bricks (shared core):
Bricks/modules/Core/Core/Content/Theming/Tokens/ThemeTokens.swift- All token path constantsBricks/modules/Core/Core/Content/Public Interface/UIConfig.swift- UIConfig protocolBricks/modules/Core/Core/Content/Public Interface/TextLabelConfig.swift- TypographyTypeProtocolBricks/modules/Core/Core/Content/Theming/CellsThemingLayoutManager/- Cell layout themingBricks/modules/Client/Client/App/UIConfigurator.swift- UIConfigurator injection
Customer app (files to create/modify):
UI Customisation/Content/ConfigParser.swift- Token resolutionUI Customisation/Content/AppTheming.swift- Color, typography, radius, border enumsUI Customisation/Content/UIConfigurator.swift- UIConfig implementationUI Customisation/Content/FontBook.swift- Custom font mappingResources/ConfigTokens/tokens.json- Semantic token routingResources/ConfigTokens/colors.json- Color paletteResources/ConfigTokens/sizes.json- Sizes and spacing
Adding a New Token
- Add the token constant to
ThemeTokens.swiftin Core with the appropriate path - Add the corresponding entry in
tokens.jsonwith$typeand$value - If the value references a new color/size, add it to
colors.jsonorsizes.json - Use
ConfigParser.colorFromToken()orConfigParser.sizeFromToken()in the code that needs the value
Modifying a Token Value
To change a color or size without touching Swift code, edit the JSON files directly:
- To change a specific color shade, update the
$valueincolors.json - To remap a semantic token to a different shade, update the
$valuereference intokens.json - To change a spacing or radius value, update the
$valueinsizes.json
Bootstrap Sequence
The initialization order when a customer app launches, covering AppDelegate setup, SceneDelegate module injection, and the conditions required before the first screen appears.
Coordinator Pattern
How navigation works in Youll apps, from the base protocol to the full coordinator hierarchy, deep link routing, and inter-coordinator communication.