From b84e32c04a0390a676279d0bd8f04cdf23e00612 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sat, 10 Jan 2026 17:27:04 -0600 Subject: [PATCH] Add THEME_GUIDE.md for app color theming documentation --- Sources/Bedrock/Theme/THEME_GUIDE.md | 407 +++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 Sources/Bedrock/Theme/THEME_GUIDE.md diff --git a/Sources/Bedrock/Theme/THEME_GUIDE.md b/Sources/Bedrock/Theme/THEME_GUIDE.md new file mode 100644 index 0000000..2ddec04 --- /dev/null +++ b/Sources/Bedrock/Theme/THEME_GUIDE.md @@ -0,0 +1,407 @@ +# App Theme Guide + +This guide explains how to create a custom color theme for your iOS app using the Bedrock theming system. The system uses protocols to define semantic color categories, making it easy to maintain consistent colors throughout your app and switch themes if needed. + +## Overview + +The theming system consists of: + +1. **Color Protocols** — Define what colors your theme must provide +2. **Color Enums** — Your app's concrete color implementations +3. **Theme Enum** — Combines all color providers into one theme +4. **Typealiases** — Provide clean, short names for use in views + +## Step 1: Create Your Theme File + +Create a file called `YourAppTheme.swift` in your app's `Shared/Theme/` folder. + +```swift +import SwiftUI +import Bedrock + +// MARK: - YourApp Surface Colors + +/// Surface colors for backgrounds and containers. +public enum YourAppSurfaceColors: SurfaceColorProvider { + /// Primary background - darkest/base color + public static let primary = Color(red: 0.08, green: 0.06, blue: 0.10) + + /// Secondary/elevated surface - slightly lighter + public static let secondary = Color(red: 0.12, green: 0.08, blue: 0.14) + + /// Tertiary/card surface - more elevated + public static let tertiary = Color(red: 0.16, green: 0.11, blue: 0.18) + + /// Overlay background (for sheets/modals) + public static let overlay = Color(red: 0.10, green: 0.07, blue: 0.12) + + /// Card/grouped element background + public static let card = Color(red: 0.14, green: 0.10, blue: 0.16) + + /// Subtle fill for grouped content sections + public static let groupedFill = Color(red: 0.12, green: 0.09, blue: 0.14) + + /// Section fill for list sections + public static let sectionFill = Color(red: 0.16, green: 0.12, blue: 0.18) +} + +// MARK: - YourApp Text Colors + +/// Text colors for labels and content. +public enum YourAppTextColors: TextColorProvider { + /// Primary text - highest emphasis (usually white for dark themes) + public static let primary = Color.white + + /// Secondary text - muted + public static let secondary = Color.white.opacity(Design.Opacity.accent) + + /// Tertiary text - hints and captions + public static let tertiary = Color.white.opacity(Design.Opacity.medium) + + /// Disabled text + public static let disabled = Color.white.opacity(Design.Opacity.light) + + /// Placeholder text + public static let placeholder = Color.white.opacity(Design.Opacity.overlay) + + /// Inverse text (for contrasting backgrounds like buttons) + public static let inverse = Color.black +} + +// MARK: - YourApp Accent Colors + +/// Accent colors for interactive elements and branding. +public enum YourAppAccentColors: AccentColorProvider { + /// Primary accent - your main brand color + public static let primary = Color(red: 0.85, green: 0.25, blue: 0.45) + + /// Light variant - softer version + public static let light = Color(red: 0.95, green: 0.45, blue: 0.60) + + /// Dark variant - deeper version + public static let dark = Color(red: 0.65, green: 0.18, blue: 0.35) + + /// Secondary accent - for contrast or secondary actions + public static let secondary = Color(red: 1.0, green: 0.95, blue: 0.90) +} + +// MARK: - YourApp Button Colors + +/// Button-specific colors for different button states and types. +public enum YourAppButtonColors: ButtonColorProvider { + /// Light gradient color for primary buttons + public static let primaryLight = Color(red: 0.95, green: 0.40, blue: 0.55) + + /// Dark gradient color for primary buttons + public static let primaryDark = Color(red: 0.75, green: 0.20, blue: 0.40) + + /// Secondary button background + public static let secondary = Color.white.opacity(Design.Opacity.subtle) + + /// Destructive action color + public static let destructive = Color.red.opacity(Design.Opacity.heavy) + + /// Cancel button text color + public static let cancelText = Color.white.opacity(Design.Opacity.strong) +} + +// MARK: - YourApp Status Colors + +/// Semantic status colors for feedback. +public enum YourAppStatusColors: StatusColorProvider { + /// Success/positive state + public static let success = Color(red: 0.2, green: 0.8, blue: 0.4) + + /// Warning state + public static let warning = Color(red: 1.0, green: 0.75, blue: 0.2) + + /// Error/negative state + public static let error = Color(red: 0.9, green: 0.3, blue: 0.3) + + /// Informational state + public static let info = Color(red: 0.5, green: 0.7, blue: 0.95) +} + +// MARK: - YourApp Border Colors + +/// Border and divider colors. +public enum YourAppBorderColors: BorderColorProvider { + /// Subtle border - barely visible + public static let subtle = Color.white.opacity(Design.Opacity.subtle) + + /// Standard border + public static let standard = Color.white.opacity(Design.Opacity.hint) + + /// Emphasized border - more visible + public static let emphasized = Color.white.opacity(Design.Opacity.light) + + /// Selected/active border - uses accent color + public static let selected = YourAppAccentColors.primary.opacity(Design.Opacity.medium) +} + +// MARK: - YourApp Interactive Colors + +/// Colors for interactive states (hover, pressed, selected). +public enum YourAppInteractiveColors: InteractiveColorProvider { + /// Selected state + public static let selected = YourAppAccentColors.primary.opacity(Design.Opacity.selection) + + /// Hover/highlight state + public static let hover = Color.white.opacity(Design.Opacity.subtle) + + /// Pressed state + public static let pressed = Color.white.opacity(Design.Opacity.hint) + + /// Focus ring color + public static let focus = YourAppAccentColors.light +} + +// MARK: - YourApp Theme + +/// The complete theme combining all color providers. +public enum YourAppTheme: AppColorTheme { + public typealias Surface = YourAppSurfaceColors + public typealias Text = YourAppTextColors + public typealias Accent = YourAppAccentColors + public typealias Button = YourAppButtonColors + public typealias Status = YourAppStatusColors + public typealias Border = YourAppBorderColors + public typealias Interactive = YourAppInteractiveColors +} +``` + +## Step 2: Create Convenience Typealiases + +Add short typealiases at the bottom of your theme file for cleaner usage in views: + +```swift +// MARK: - Convenience Typealiases + +/// Short typealiases for cleaner usage throughout the app. +/// +/// Usage: +/// ```swift +/// .background(AppSurface.primary) +/// .foregroundStyle(AppAccent.primary) +/// .foregroundStyle(AppText.secondary) +/// ``` +typealias AppSurface = YourAppSurfaceColors +typealias AppText = YourAppTextColors +typealias AppAccent = YourAppAccentColors +typealias AppButton = YourAppButtonColors +typealias AppStatus = YourAppStatusColors +typealias AppBorder = YourAppBorderColors +typealias AppInteractive = YourAppInteractiveColors +``` + +## Step 3: Use Theme Colors in Views + +### Background Colors + +```swift +struct ContentView: View { + var body: some View { + VStack { + // Primary background + } + .background(AppSurface.primary) + } +} + +struct SettingsView: View { + var body: some View { + ScrollView { + VStack { + SettingsCard(backgroundColor: AppSurface.card, borderColor: AppBorder.subtle) { + // Card content + } + } + } + .background(AppSurface.primary) + } +} +``` + +### Text Colors + +```swift +// Primary text (titles, important content) +Text("Settings") + .foregroundStyle(AppText.primary) + +// Secondary text (subtitles, descriptions) +Text("Adjust your preferences") + .foregroundStyle(AppText.secondary) + +// Tertiary text (hints, captions) +Text("Last updated 5 minutes ago") + .foregroundStyle(AppText.tertiary) + +// Disabled text +Text("Feature unavailable") + .foregroundStyle(AppText.disabled) +``` + +### Accent Colors + +```swift +// Toggles and interactive elements +SettingsToggle( + title: "Enable Feature", + isOn: $isEnabled, + accentColor: AppAccent.primary +) + +// Buttons +Button("Save") { save() } + .foregroundStyle(AppAccent.primary) + +// Section headers +SettingsSectionHeader( + title: "Display", + systemImage: "eye", + accentColor: AppAccent.primary +) +``` + +### Status Colors + +```swift +// Success indicator +Image(systemName: "checkmark.circle.fill") + .foregroundStyle(AppStatus.success) + +// Warning badge +Text("Premium") + .foregroundStyle(AppStatus.warning) + +// Error message +Text("Something went wrong") + .foregroundStyle(AppStatus.error) +``` + +### Border Colors + +```swift +RoundedRectangle(cornerRadius: Design.CornerRadius.medium) + .strokeBorder(AppBorder.subtle, lineWidth: Design.LineWidth.thin) + +// Selected state +RoundedRectangle(cornerRadius: Design.CornerRadius.medium) + .strokeBorder(AppBorder.selected, lineWidth: Design.LineWidth.medium) +``` + +## Color Categories Reference + +| Category | Purpose | Examples | +|----------|---------|----------| +| **Surface** | Backgrounds, containers | Screen background, cards, modals | +| **Text** | All text content | Titles, labels, hints, placeholders | +| **Accent** | Brand colors, interactive elements | Toggles, buttons, links | +| **Button** | Button-specific colors | Primary buttons, destructive actions | +| **Status** | Semantic feedback | Success, warning, error, info | +| **Border** | Borders and dividers | Card borders, separators | +| **Interactive** | State colors | Selected, hover, pressed, focus | + +## Design.Opacity Reference + +The `Design.Opacity` enum provides consistent opacity values: + +| Name | Value | Use Case | +|------|-------|----------| +| `verySubtle` | 0.03 | Barely visible fills | +| `subtle` | 0.08 | Subtle backgrounds | +| `hint` | 0.12 | Hint borders | +| `light` | 0.25 | Light overlays | +| `overlay` | 0.35 | Overlay backgrounds | +| `medium` | 0.50 | Medium emphasis | +| `accent` | 0.65 | Accented elements | +| `strong` | 0.75 | Strong emphasis | +| `heavy` | 0.85 | Heavy emphasis | +| `selection` | 0.15 | Selection highlights | + +## Tips + +### 1. Use Semantic Names + +Always use the semantic color names, not raw values: + +```swift +// ✅ Good - semantic name +.foregroundStyle(AppText.secondary) + +// ❌ Bad - raw opacity value +.foregroundStyle(.white.opacity(0.65)) +``` + +### 2. Keep Colors Centralized + +Never define colors inline. Add them to your theme file: + +```swift +// ✅ Good - uses theme +.background(AppSurface.card) + +// ❌ Bad - inline color +.background(Color(red: 0.14, green: 0.10, blue: 0.16)) +``` + +### 3. Use Appropriate Text Hierarchy + +- `AppText.primary` — Titles, important content +- `AppText.secondary` — Subtitles, descriptions +- `AppText.tertiary` — Captions, hints, metadata +- `AppText.disabled` — Unavailable content +- `AppText.placeholder` — Input placeholders + +### 4. Consistent Accent Usage + +Use the accent color for: +- Toggle switches +- Buttons (primary actions) +- Links +- Selected states +- Active indicators + +### 5. Dark Theme Considerations + +For dark themes: +- Surface colors get lighter as elevation increases +- Text is white with varying opacity +- Ensure sufficient contrast (WCAG AA: 4.5:1 for text) + +## Migrating Existing Views + +If you have views using hardcoded colors, migrate them: + +```swift +// Before +.foregroundStyle(.white) +.foregroundStyle(.white.opacity(0.65)) +.background(Color(red: 0.14, green: 0.10, blue: 0.16)) + +// After +.foregroundStyle(AppText.primary) +.foregroundStyle(AppText.secondary) +.background(AppSurface.card) +``` + +## Light Theme Support + +To support light themes, create a second set of color providers: + +```swift +public enum YourAppLightSurfaceColors: SurfaceColorProvider { + public static let primary = Color.white + public static let secondary = Color(red: 0.96, green: 0.96, blue: 0.98) + // ... etc +} + +public enum YourAppLightTextColors: TextColorProvider { + public static let primary = Color.black + public static let secondary = Color.black.opacity(0.65) + // ... etc +} +``` + +Then use `@Environment(\.colorScheme)` to switch between themes in your views, or define typealiases that automatically select the correct theme based on system appearance.