Skip to content

iOS Token Structure: openedx-app-ios

Source: Theme/Theme/Theme.swift, Theme/Theme/SwiftGen/ThemeAssets.swift, Theme/Theme/Assets.xcassets/, Theme/Theme/Fonts/fonts.jsonConfidence: CONFIRMED — verified directly against codebase


Summary

The app uses a Theme module as the single source of truth for all design tokens. Token definitions exist at a semantic / component level only — there is no primitive layer (no gray-100, blue-500, no spacing scale). Colors are stored in Xcode Asset Catalogs with light/dark mode pairs. Typography and shapes are defined directly in Swift. There is no spacing token system; all padding and layout values are hardcoded at the call site.


Token Architecture Overview

Theme/
├── Assets.xcassets/          # Color values (sRGB, light+dark pairs per token)
│   └── Colors/
│       ├── TextColor/        # textPrimary, textSecondary, etc.
│       ├── TextInput/        # textInputBackground, textInputStroke, etc.
│       ├── CardView/         # cardViewBackground, cardViewStroke
│       ├── CourseDates/      # timeline colors, dates section
│       ├── PrimaryCard/      # primaryCardCautionBG, etc.
│       ├── ProgressLine/     # onProgress, progressDone, progressSkip, selectedAndDone
│       ├── ResumeButton/     # resumeButtonBG, resumeButtonText
│       ├── SecondaryButton/  # border, text, BG
│       ├── SlidingTabBar/    # slidingTextColor, etc.
│       ├── Snackbar/         # error, warning, info, text
│       ├── Tabbar/           # active, inactive, BG
│       └── [root-level]      # accentColor, background, alert, warning, white, etc.

├── Fonts/
│   └── fonts.json            # Font family name map (weight → PostScript name)

└── Theme.swift               # Public API: Theme.Colors, Theme.Fonts, Theme.Shapes
    SwiftGen/ThemeAssets.swift # Auto-generated asset enum (SwiftGen)

Access pattern in app code:

swift
Theme.Colors.textPrimary        // → SwiftUI Color
Theme.UIColors.textPrimary      // → UIColor (bridge for UIKit contexts)
Theme.Fonts.bodyLarge           // → Font
Theme.Shapes.buttonShape        // → Shape

Token Levels: What Exists vs. What's Missing

Token LevelColorsTypographySpacingShapes
Primitive (raw values, unnamed)✗ Not present✗ Not present✗ Not present✗ Not present
Semantic (named by role/intent)✓ Present✓ Present✗ Not presentPartial
Component (scoped to a component)Partial (folder grouping)✗ Not present✗ Not presentPartial

Bottom line: The app skips primitive tokens entirely. Every token is named by semantic intent or component context. There is no underlying palette that semantic tokens reference — the raw hex values live directly in the Asset Catalog.


Colors

How They're Defined

Color values are stored in Xcode Asset Catalog .colorset files as sRGB float components. Each colorset contains a light mode and optionally a dark mode variant.

Example — TextPrimary.colorset/Contents.json:

json
{
  "colors": [
    {
      "color": { "color-space": "srgb", "components": { "red": "0.098", "green": "0.129", "blue": "0.184", "alpha": "1.000" } },
      "idiom": "universal"
    },
    {
      "appearances": [{ "appearance": "luminosity", "value": "dark" }],
      "color": { "color-space": "srgb", "components": { "red": "1.000", "green": "1.000", "blue": "1.000", "alpha": "1.000" } },
      "idiom": "universal"
    }
  ]
}

Colors are auto-enumerated by SwiftGen into ThemeAssets, then re-exposed as Theme.Colors static properties with nonisolated(unsafe) modifiers (to support white-label runtime overrides).

Token Inventory (91 total)

General / Surface

TokenNotes
backgroundApp background
loginBackgroundLogin screen background
backgroundStrokeBorder on background surfaces
cardViewBackgroundCard surface
cardViewStrokeCard border
commentCellBackgroundDiscussion comment cell
courseCardBackgroundCourse card surface
courseCardShadowCourse card drop shadow
datesSectionBackgroundDates tab section
datesSectionStrokeDates tab section border
deleteAccountBGDelete account screen
loginNavigationTextLogin nav bar text
shadowColorGeneric shadow
shadeOverlay/scrim
whitePure white (used as explicit override)

Accent / Brand

TokenNotes
accentColorPrimary brand color
accentXColorAlternate accent
accentButtonColorButton variant of accent

Text

TokenNotes
textPrimaryPrimary body text
textSecondarySecondary/muted text
textSecondaryLightLight variant
textSecondaryDarkDark variant
textInputBackgroundFocused input BG
textInputStrokeFocused input border
textInputUnfocusedBackgroundUnfocused input BG
textInputUnfocusedStrokeUnfocused input border
textInputTextColorInput text
textInputPlaceholderColorPlaceholder text

Buttons

TokenNotes
styledButtonTextPrimary button text
disabledButtonDisabled button BG
disabledButtonTextDisabled button text
primaryButtonTextColorPrimary button text (alt)
secondaryButtonBGColorSecondary button BG
secondaryButtonBorderColorSecondary button border
secondaryButtonTextColorSecondary button text
resumeButtonBGResume course button BG
resumeButtonTextResume course button text
socialAuthColorSocial login button
toggleSwitchColorToggle/switch

Semantic Status

TokenNotes
alertError/alert
irreversibleAlertDestructive action alert
warningWarning state
warningTextWarning text
successSuccess state
infoColorInformational

Progress / Course

TokenNotes
onProgressIn-progress state
progressDoneCompleted state
progressSkipSkipped state
progressSelectedAndDoneSelected + completed
courseProgressBGCourse progress bar BG
circleProgressBGCircle progress BG
progressLineBGProgress line BG
progressPercentageProgress percentage text
assignmentColorAssignment item stroke

Snackbar

TokenNotes
snackbarErrorColorError snackbar
snackbarWarningColorWarning snackbar
snackbarInfoColorInfo snackbar
snackbarTextColorSnackbar text

Timeline (Course Dates)

TokenNotes
todayTimelineColorToday marker
thisWeekTimelineColorThis week
nextWeekTimelineColorNext week
upcomingTimelineColorUpcoming
pastDueTimelineColorPast due

Course Headers

TokenNotes
primaryHeaderColorPrimary course header
secondaryHeaderColorSecondary course header

Primary Card States

TokenNotes
primaryCardCautionBGCaution state card
primaryCardUpgradeBGUpgrade CTA card
primaryCardProgressBGProgress state card
TokenNotes
navigationBarTintColorNav bar icons/text
tabbarActiveColorActive tab icon
tabbarInactiveColorInactive tab icon
tabbarBGColorTab bar background

Sliding Tab Bar

TokenNotes
slidingTextColorUnselected tab text
slidingSelectedTextColorSelected tab text
slidingStrokeColorTab bar border

Other

TokenNotes
avatarStrokeAvatar border
certificateForegroundCertificate illustration
loginNavigationTextAuth flow nav text

White-Label Override

Theme.Colors.update(...) allows runtime replacement of any color, enabling multi-tenant theming without recompilation. The update() function signature is partial — not all 91 tokens are overridable via it (only ~35 are exposed), leaving the remainder as asset-only values.


Typography

How It's Defined

Font tokens are Swift static constants in Theme.Fonts. Font weight is abstracted through a FontIdentifier enum mapped to PostScript names via fonts.json. This allows white-label font replacement: swap fonts.json to change the entire typeface.

Font weight map (fonts.json):

json
{
  "light":    "SFPro-Light",
  "regular":  "SFPro-Regular",
  "medium":   "SFPro-Medium",
  "semiBold": "SFPro-Semibold",
  "bold":     "SFPro-Bold"
}

Type Scale

The scale follows Material Design role vocabulary (display, headline, title, body, label) × size (large, medium, small). Sizes are hardcoded integers — there is no underlying spacing or size scale they reference.

TokenWeightSize
displayLargeRegular57pt
displayMediumRegular45pt
displaySmallBold36pt
headlineLargeRegular32pt
headlineMediumRegular28pt
headlineSmallRegular24pt
titleLargeBold22pt
titleMediumSemiBold18pt
titleSmallMedium14pt
bodyLargeRegular16pt
bodyMediumRegular14pt
bodySmallRegular12pt
bodyMicroLight11pt
labelLargeMedium14pt
labelMediumRegular12pt
labelSmallRegular10pt

UIKit bridge (Theme.UIFonts): Three UIFont variants exposed for contexts where SwiftUI Font cannot be used: labelSmall, labelLarge, titleMedium.


Shapes / Radius

Defined as Theme.Shapes static properties. Mix of configurable and hardcoded values.

TokenValueNotes
screenBackgroundRadius24.0ptFixed
cardImageRadius10.0ptFixed
buttonCornersRadius8.0ptConfigurable via isRoundedCorners flag
cardShape12pt all cornersFixed
unitButtonShape21pt all cornersFixed (pill-like)
textInputShape8pt (or 0 if not rounded)Responds to isRoundedCorners
buttonShapebuttonCornersRadius (or 0)Responds to isRoundedCorners
roundedScreenBackgroundShape24pt all cornersFull rounded screen
roundedScreenBackgroundShapeCroppedBottom24pt top onlySheet-style partial rounding

isRoundedCorners is a global toggle (default: true) that flattens button and input corners for operators who prefer a rectangular aesthetic.


Spacing

There is no spacing token system. No Theme.Spacing struct exists. All padding, gap, and margin values are hardcoded inline at each call site across the codebase. The only spacing-adjacent value in Theme is the default padding: CGFloat = 8 on InputFieldBackground, which is a component default, not a token.

This is the most significant gap relative to a mature token architecture.


Key Design Observations

What the token system does well

  • Semantic naming throughout — no blue1 or color23; every token has a clear intent
  • Dark mode built in — every .colorset is a light/dark pair, handled at the asset level
  • White-label readyColors.update() and fonts.json substitution enable multi-tenant theming
  • SwiftGen keeps the asset enum in sync automatically, preventing stale references
  • Concurrency-safenonisolated(unsafe) on mutable statics is appropriate for Swift 6 concurrency

What's missing vs. a token hierarchy

  • No primitive layer — raw color values (hex/sRGB) live directly in Asset Catalog files with no named palette. There's no brand-blue that accentColor references.
  • No spacing scale — no 4 / 8 / 12 / 16 / 24 / 32 scale; all spacing is magic numbers in views
  • No elevation tokens — shadow values are not tokenized; shadowColor is a color only, no blur/offset definitions
  • Inconsistent component scoping — some colors are folder-grouped by component (SecondaryButton/, Snackbar/) but the grouping is cosmetic only; it's not enforced in code
  • Partial update() coverage — 91 colors are defined but only ~35 are in the update() override signature; late-added tokens are missing from the white-label path
  • No line-height or letter-spacing tokens — typography tokens define only size and weight; line-height and tracking are left to SwiftUI defaults or hardcoded per view

Files Referenced

FilePurpose
Theme/Theme/Theme.swiftPublic token API — Theme.Colors, Theme.Fonts, Theme.Shapes
Theme/Theme/SwiftGen/ThemeAssets.swiftAuto-generated SwiftGen enum for all color and image assets
Theme/Theme/Assets.xcassets/Colors/**Raw color values (sRGB, light/dark pairs)
Theme/Theme/Fonts/FontParser.swiftFont weight → PostScript name resolution
Theme/Theme/Fonts/fonts.jsonFont family name map (white-label font config)
Theme/Theme/Helpers/RoundedCorners.swiftCustom Shape for per-corner radius control

Schema Education — Internal Research