Skip to content

Add the shared design token system to StreamCoreUI - #81

Open
nuno-vieira wants to merge 14 commits into
developfrom
core-ui-design-tokens
Open

Add the shared design token system to StreamCoreUI#81
nuno-vieira wants to merge 14 commits into
developfrom
core-ui-design-tokens

Conversation

@nuno-vieira

@nuno-vieira nuno-vieira commented Aug 28, 2026

Copy link
Copy Markdown
Member

Adds DesignSystemTokens to StreamCoreUI: the shared colour and layout tokens every Stream SDK draws from. Fonts, icons and images stay on each product SDK.

Architecture

DesignSystemTokens is a class grouped by kind (colors, layout). Each SDK owns its own appearance (VideoAppearance, later ChatAppearance) and is constructed with a DesignSystemTokens instance. Pass the same instance into both so they reskin together.

flowchart TB
  subgraph core ["StreamCoreUI"]
    DT["DesignSystemTokens"]
    DT --> C["Colors\n175 tokens, lazy"]
    DT --> L["Layout\n66 tokens, lazy"]
  end

  subgraph video ["StreamVideoSwiftUI"]
    VA["VideoAppearance"]
    VA --> DT
    VA --> VC["VideoAppearance.Colors\n14 tokens"]
    VC --> C
  end

  subgraph chat ["StreamChat — follow-up"]
    CA["ChatAppearance"]
    CA --> DT
    CA --> CHC["ChatAppearance.Colors\n37 chat* tokens"]
    CHC --> C
  end
Loading
let tokens = DesignSystemTokens()
tokens.colors.accentPrimary = .red
tokens.layout.spacingMd = 16
let videoAppearance = VideoAppearance(tokens: tokens)
let chatAppearance = ChatAppearance(tokens: tokens)

Colour and layout tokens are lazy var. Override the brand and chrome ramps (or a scale token) before the first read so semantic tokens pick up the override.

CoreUI does not import Chat or Video. Product colours live on each SDK's appearance (videoAppearance.colors.indicatorSpeaking). Video keeps the existing Appearance for views until they migrate.

Token scope

The design-system-tokens generator emits one flat file covering every Stream product. Product groups live on the SDK that owns them:

Shared (here) Chat Video
Colour tokens 175 37 14
Layout tokens 66 8 0

The split is applied by hand. DesignSystem/TokenScope.md records it so a re-sync can re-apply it. Moving that upstream into the generator is IOS-2000.

UIColor.init(light:dark:) stays internal. Chat already vends a public equivalent; a second one would be ambiguous at any call site that imports both modules.

Testing

StreamCoreUI tests cover instance identity and the lazy-derivation boundary (override a ramp before vs after the first read).

Follow-ups

  • Video adoption: GetStream/stream-video-swift#1258, pinned to this branch until this merges and ships.
  • Chat adoption: IOS-1999. Two things need deciding there: chatBackgroundMention resolves to a raw ramp colour that is internal here, and which UIColor.init(light:dark:) survives once Chat links CoreUI.

Brings the generated design system tokens into StreamCoreUI so Video and
Chat build against one source instead of each keeping a copy.

ColorPalette holds the semantic colour tokens and DesignSystemTokens the
layout ones (spacing, radii, icon sizing, stroke widths, elevations). Both
are classes of lazy properties, so semantic tokens derive from the ramps
and foundation scale: overriding brand500 or radiusXl before the first read
cascades to everything built on it.

The full token set is kept, including the messaging surfaces, so neither
SDK has to re-derive what the other needs on every re-sync.
@nuno-vieira
nuno-vieira requested a review from a team as a code owner August 28, 2026 16:39
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
10 Errors
🚫 Please remove period from end of commit subject line.
2c42434
🚫 Please remove period from end of commit subject line.
57fa486
🚫 Please remove period from end of commit subject line.
74ae196
🚫 Please remove period from end of commit subject line.
30a04cd
🚫 Please remove period from end of commit subject line.
87d3e82
🚫 Please remove period from end of commit subject line.
902c8e4
🚫 Please remove period from end of commit subject line.
663b5c4
🚫 Please remove period from end of commit subject line.
88f83ef
🚫 Please remove period from end of commit subject line.
6233c5a
🚫 Please remove period from end of commit subject line.
a2502ae
1 Warning
⚠️ Big PR

Generated by 🚫 Danger

The generator emits one flat token file covering every Stream product, but
each SDK only draws part of it. Keep the shared set here and hand the
messaging and calling groups to the SDKs that own them, so neither product
exposes the other's vocabulary. TokenScope.md records the split, since it is
applied by hand and has to survive the next re-sync.

Add fonts and generic icons alongside the tokens, and a SharedAppearance that
holds all four. Every SDK defaults to the same instance, so an app running
more than one of them reskins them together.

Fonts and images are vended for both UIKit and SwiftUI rather than bridged: a
symbol that reaches SwiftUI through Image(uiImage:) stops scaling with the
surrounding font and needs its rendering mode forced before it tints.
ColorPalette and DesignSystemTokens now live on Appearance. Product SDKs
attach their own configuration through a bag store, so CoreUI never imports
Chat or Video. @dynamicMemberLookup on ColorPalette lets each SDK flatten
product tokens onto the palette without a public .chat / .call property.
Icons stay on each product SDK; this module is not the place for a shared image set.
The product prefix is Video, not Call.
Appearance is UI configuration and is only meant to be read and written on the main thread, so the bag stores no longer need an unchecked Sendable escape.
Shared typography is out of scope for this pass, so CoreUI only vends the shared colour palette and layout tokens.
Appearance is a shared configuration object, and marking it MainActor forced assumeIsolated at every injection boundary. Keep it a plain class and leave thread use to the caller.
The type is not actor-isolated, so the compiler requires an explicit escape for the singleton.
…tokens.

Each product owns its own appearance; Core now exposes a class with lazy colour and layout groups that Chat and Video can pass around together.
The type is the shared design-system token bag, so the name should match.
@nuno-vieira
nuno-vieira marked this pull request as ready for review August 31, 2026 16:27
Comment on lines +59 to +68
public lazy var avatarPaletteBackground1: UIColor = UIColor(light: .blue150, dark: .blue600)
public lazy var avatarPaletteBackground2: UIColor = UIColor(light: .cyan150, dark: .cyan600)
public lazy var avatarPaletteBackground3: UIColor = UIColor(light: .green150, dark: .green600)
public lazy var avatarPaletteBackground4: UIColor = UIColor(light: .purple150, dark: .purple600)
public lazy var avatarPaletteBackground5: UIColor = UIColor(light: .yellow150, dark: .yellow600)
public lazy var avatarPaletteText1: UIColor = UIColor(light: .blue900, dark: .blue100)
public lazy var avatarPaletteText2: UIColor = UIColor(light: .cyan900, dark: .cyan100)
public lazy var avatarPaletteText3: UIColor = UIColor(light: .green900, dark: .green100)
public lazy var avatarPaletteText4: UIColor = UIColor(light: .purple900, dark: .purple100)
public lazy var avatarPaletteText5: UIColor = UIColor(light: .yellow900, dark: .yellow100)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably ask @jurgenploeger to clean up some of these. We dropped variable colors and just use the default.

Comment on lines +65 to +66
public lazy var inputRadiusOptionCard: CGFloat = radiusXl
public lazy var inputRadiusPollOptionInput: CGFloat = radiusXl

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Look chat specific, buy probably fine

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, yes they kook chat specific, that is the thing, AI will make mistakes on interperting these, so we need @jurgenploeger to set some metadata or something to identify Chat-only or - Video-only stuff

///
/// Tokens derive lazily, so override the ramps before the first read.
extension DesignSystemTokens {
public final class Colors {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MainActor? These are mutable and meant for UI.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal is to make it MainActor, but doing so produced some changes in views, but that was before I using this already in some views. So I can probably but back main actor

Comment on lines +23 to +27
public final class DesignSystemTokens {
/// Shared colour tokens.
public var colors: Colors
/// Shared layout tokens.
public var layout: Layout

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we go with MainActor for this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is the goal

Comment on lines +10 to +15
private lazy var subject: DesignSystemTokens! = .init()

override func tearDown() {
subject = nil
super.tearDown()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly confusing compared to the typical setup and teardown we have for other tests. I would personally prefer the old style of explicit setUp although this works because how xctest runs tests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I didnt noticed this, maybe it is the pattern here on video?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants