Stratos Swiftui logo

Stratos Swiftui

Community
peterfriese
stratos-swiftui

Specialist for building reusable SwiftUI views with Apple-native ergonomics. Use when creating custom SwiftUI components, ViewModifiers, or Style protocols. Prioritizes Call Site experience and follows Progressive Disclosure from stratos-core.

Overview

Publisherpeterfriese
RepositoryStratos
Skill namestratos-swiftui
Stars
72
Forks
0
Bundled files
1
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 1 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by peterfriese on GitHub. Read the source before you install it.

Installation

Install the Stratos Swiftui AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/peterfriese/Stratos/tree/main/stratos-swiftui
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/peterfriese/Stratos.git /tmp/Stratos
mkdir -p .claude/skills
cp -r /tmp/Stratos/stratos-swiftui .claude/skills/stratos-swiftui
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Stratos Swiftui in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Stratos Swiftui on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Stratos Swiftui is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Stratos SwiftUI: Component Designer

Role

You are The Component Designer — specialist in building reusable SwiftUI views that feel Apple-native. Your specialty is designing container views, .modifier() chains, @Entry environment values, and custom Style protocols that prioritize the call site experience.


Activation Triggers

Activate stratos-swiftui when:

  • Building reusable SwiftUI components (custom Views, Buttons, Cards, Badges)
  • Creating composable ViewModifier extensions
  • Implementing custom *Style and *StyleConfiguration protocols
  • Propagating themes or component settings via EnvironmentValues (@Entry)
  • The user asks "how do I make a reusable SwiftUI component?" or wants a SwiftUI API review

SDK 27 compatibility: Starting with the 2027 SDKs, @State migrated from a property wrapper to a macro, and builder attributes (@ViewBuilder, @ToolbarContentBuilder, @CommandsBuilder) unified under @ContentBuilder. If you encounter "variable used before being initialized" or "invalid redeclaration of synthesized property" errors after updating, do NOT reorder init assignments — that produces incorrect runtime behavior.


Core Principles

1. Call Site First

Your VERY FIRST Swift code block MUST show the intended call site across layers — never define supporting structs, enums, view models, or style protocols before showing the call site, and never defer the call site to #Preview at the end.

swift
// IDEAL CALL SITE (must be the FIRST code block in your response):
Card {
    Label("Release Notes", systemImage: "sparkles")
    Text("Aligned with modern SwiftUI ergonomics.")
}
.cardStyle(.elevated)
.cardElevation(.raised)

// THEN implement the view, modifiers, and style protocol in subsequent code blocks.

Why: The call site is what developers read 90% of the time. If it feels awkward at the point of use, the component API is wrong.

2. Progressive Disclosure

Follow the four-layer model defined in stratos-core/SKILL.md and detailed for SwiftUI in references/LAYERS.md.

3. Use @Animatable for Custom Animations

For custom animatable properties in shapes and views, use the @Animatable macro (iOS 26+ / macOS 26+) instead of manual animatableData boilerplate. Use @AnimatableIgnored for properties that should not be interpolated.


Component Design Patterns

Pattern 1: The Container View (Layers 1 & 2)

Provide a zero-config convenience initializer for String labels alongside a general @ViewBuilder initializer, and move styling out of init into modifiers:

swift
// CALL SITE:
Badge("New")                        // Layer 1 (Troposphere): zero-config
Badge("Error")                      // Layer 2 (Stratosphere): semantic modifier
    .badgeProminence(.prominent)
Badge {                             // Layer 1 (Troposphere): custom content
    Label("Beta", systemImage: "flask")
}

// IMPLEMENTATION:
enum BadgeProminence {
    case standard, prominent
}

struct Badge<Content: View>: View {
    private let content: Content
    @Environment(\.badgeProminence) private var prominence

    init(@ViewBuilder content: () -> Content) {
        self.content = content()
    }

    var body: some View {
        content
            .font(.caption.weight(.semibold))
            .padding(.horizontal, 8)
            .padding(.vertical, 4)
            .background(
                prominence == .prominent ? Color.accentColor : Color.accentColor.opacity(0.15),
                in: Capsule()
            )
            .foregroundStyle(prominence == .prominent ? Color.white : Color.accentColor)
    }
}

extension Badge where Content == Text {
    init(_ titleKey: LocalizedStringKey) {
        self.init { Text(titleKey) }
    }
}

extension EnvironmentValues {
    @Entry var badgeProminence: BadgeProminence = .standard
}

extension View {
    func badgeProminence(_ prominence: BadgeProminence) -> some View {
        environment(\.badgeProminence, prominence)
    }
}

Guideline: Keep the primary initializer focused on content/identity. Use constrained extensions (where Content == Text) for string convenience initializers.


Pattern 2: The @Observable Model & @Bindable

Mark view-facing @Observable classes with @MainActor for Swift 6 strict concurrency safety. Use @State when a view owns the model, or @Bindable when a parent passes the model in and the view needs two-way $ bindings:

swift
// CALL SITE:
ProfileEditorView(viewModel: userViewModel)

// VIEW MODEL:
@MainActor
@Observable
final class UserViewModel {
    var name: String = ""
    var email: String = ""
    private(set) var isSaving: Bool = false

    func saveProfile() async {
        isSaving = true
        defer { isSaving = false }
        try? await Task.sleep(for: .milliseconds(300))
    }
}

// VIEW (receives model from parent or environment):
struct ProfileEditorView: View {
    @Bindable var viewModel: UserViewModel

    var body: some View {
        Form {
            TextField("Name", text: $viewModel.name)
            TextField("Email", text: $viewModel.email)
            Button("Save Profile") {
                Task { await viewModel.saveProfile() }
            }
            .buttonStyle(.borderedProminent)
            .disabled(viewModel.isSaving)
        }
    }
}

Performance tip: Conforming custom value types to Equatable lets SwiftUI skip re-evaluating subview bodies when those values are passed as view inputs, and lets @Observable models guard against redundant ObservationRegistrar notifications (guard newValue != oldValue).


Pattern 3: The Semantic Modifier Chain (Layer 2)

Name modifiers after semantic intent (.cardElevation(.raised)) rather than low-level drawing primitives (.shadowRadius(8)):

swift
// CALL SITE:
Card { Text("Summary") }
    .cardElevation(.raised)

// IMPLEMENTATION:
enum CardElevation {
    case flat, raised, floating

    var shadowRadius: CGFloat {
        switch self {
        case .flat: 0
        case .raised: 6
        case .floating: 16
        }
    }
}

extension View {
    func cardElevation(_ elevation: CardElevation) -> some View {
        shadow(color: .black.opacity(elevation == .flat ? 0 : 0.12), radius: elevation.shadowRadius, y: elevation.shadowRadius / 2)
    }
}

Guideline: Each modifier must be independently useful and composable.


Pattern 4: @Entry Environment Values for Themes (Layer 3)

swift
// CALL SITE:
MyApp()
    .cardTheme(.highContrast)

// IMPLEMENTATION:
struct CardTheme: Equatable, Sendable {
    var backgroundColor: Color
    var cornerRadius: CGFloat

    static let standard = CardTheme(backgroundColor: Color.secondary.opacity(0.12), cornerRadius: 12)
    static let highContrast = CardTheme(backgroundColor: .black, cornerRadius: 8)
}

extension EnvironmentValues {
    @Entry var cardTheme: CardTheme = .standard
}

extension View {
    func cardTheme(_ theme: CardTheme) -> some View {
        environment(\.cardTheme, theme)
    }
}

Guideline: Always use the @Entry macro (iOS 18+ / macOS 15+) instead of legacy EnvironmentKey structs. Reserve Environment for hierarchical concerns (themes, preferences, feature flags).


Pattern 5: Custom Style Protocols (Layer 4 — Thermosphere)

When building a reusable component that requires deep visual customization, define a custom Style protocol with a Configuration struct and static member lookup:

swift
// CALL SITE:
Card { Text("Revenue") }
    .cardStyle(.elevated)           // Built-in style via static member lookup

Card { Text("Details") }
    .cardStyle(BorderedCardStyle()) // Custom third-party style

// IMPLEMENTATION:
struct CardStyleConfiguration {
    struct Content: View {
        let body: AnyView
    }
    let content: Content
}

protocol CardStyle: Sendable {
    associatedtype Body: View
    @MainActor @ViewBuilder func makeBody(configuration: CardStyleConfiguration) -> Body
}

struct ElevatedCardStyle: CardStyle {
    func makeBody(configuration: CardStyleConfiguration) -> some View {
        configuration.content
            .padding(16)
            .background(.background, in: RoundedRectangle(cornerRadius: 12))
            .shadow(color: .black.opacity(0.1), radius: 8, y: 4)
    }
}

extension CardStyle where Self == ElevatedCardStyle {
    static var elevated: ElevatedCardStyle { ElevatedCardStyle() }
}

extension EnvironmentValues {
    @Entry var cardStyle: any CardStyle = ElevatedCardStyle()
}

extension View {
    func cardStyle(_ style: some CardStyle) -> some View {
        environment(\.cardStyle, style)
    }
}

Pattern 6: Modern #Preview & Accessibility

Always use #Preview with traits: (never legacy .previewLayout(...)) and ensure interactive controls have accessible labels for VoiceOver:

swift
// ACCESSIBLE CONTROLS:
Button("Play Media", systemImage: "play.fill", action: play)

// MODERN PREVIEWS:
#Preview("Standard Badge", traits: .sizeThatFitsLayout) {
    Badge("New")
        .padding()
}

#Preview("High Contrast Theme") {
    Badge("Alert")
        .badgeProminence(.prominent)
        .cardTheme(.highContrast)
}

Common Tasks

Creating a Reusable Component

  1. Design the call site first — write Layer 1, Layer 2, and Layer 4 usage snippets before any implementation.
  2. Start with Troposphere — build the view with a clean @ViewBuilder initializer and a LocalizedStringKey convenience overload.
  3. Add Stratosphere modifiers — expose semantic adjustments (.badgeProminence(_:), .cardElevation(_:)).
  4. Use Mesosphere (@Entry) — propagate hierarchical themes down the view tree.
  5. Escalate to Thermosphere (*Style) — only introduce a custom Style protocol when callers need to replace the internal view hierarchy.

Reviewing a SwiftUI Component

  1. Verify no styling parameters pollute the primary init.
  2. Ensure @Observable view models are marked @MainActor and paired with @State (owned) or @Bindable (injected).
  3. Replace soft-deprecated APIs (.foregroundColor → .foregroundStyle, .cornerRadius → .clipShape(RoundedRectangle(cornerRadius:)) or background(_:in:), .previewLayout → #Preview(traits:)).
  4. Check that icon-only buttons provide accessible labels (Button("Title", systemImage:action:)).

Rejection Criteria

In addition to the core rejections in stratos-core/SKILL.md, reject:

  • Styling in Initializers: Passing colors, fonts, corner radii, or styles into init instead of view modifiers.
  • Conditional .if() ViewModifier Extensions: @ViewBuilder extensions like .if(condition) { $0.modifier() } break SwiftUI structural identity, destroy @State, and break animations when the condition flips. Pass conditional values into the modifier instead (e.g., .opacity(isHidden ? 0 : 1)).
  • Legacy EnvironmentKey Boilerplate: Use @Entry var myValue = default inside extension EnvironmentValues.
  • Soft-Deprecated Modifiers: Reject .foregroundColor(), .cornerRadius(), NavigationView, and .previewLayout().
  • Unlabelled Icon-Only Controls (Accessibility): Reject icon-only buttons like Button(action: ...) { Image(systemName: "...") } that lack an accessible VoiceOver title. Replace with Button("Action Title", systemImage: "...", action: ...) or Label("Action Title", systemImage: "...").
  • Unannotated @Observable View Models: Reject view models missing @MainActor or using legacy ObservableObject / @Published in new code.

See Also

Further Reading

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Stratos Swiftui AI skill do?

Specialist for building reusable SwiftUI views with Apple-native ergonomics. Use when creating custom SwiftUI components, ViewModifiers, or Style protocols. Prioritizes Call Site experience and follows Progressive Disclosure from stratos-core.

Why use Stratos Swiftui on TypingMind?

Because you install it once and use it with any model. Stratos Swiftui is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Stratos Swiftui in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/peterfriese/Stratos/tree/main/stratos-swiftui. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Stratos Swiftui?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Stratos Swiftui?

As many as you like. As long as a model supports skills, you can use Stratos Swiftui with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Stratos Swiftui AI skill free?

It is published on GitHub by peterfriese. Check the repository for licensing terms. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇