Learn-skills.dev swiftui-style-driven-components

Expert guidance on SwiftUI style-driven components following Apple patterns (ButtonStyle, LabelStyle). Use when: building components with style protocols, implementing configuration patterns, adding environment-based styling, creating extensible component libraries, or reviewing component architecture.

install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/ahmadbrkt/swiftui-style-driven-components-skill/swiftui-style-driven-components" ~/.claude/skills/neversight-learn-skills-dev-swiftui-style-driven-components && rm -rf "$T"
manifest: data/skills-md/ahmadbrkt/swiftui-style-driven-components-skill/swiftui-style-driven-components/SKILL.md
source content

SwiftUI Style-Driven Components

Overview

This skill provides expert guidance on building, reviewing, and extending SwiftUI components following the style-driven architecture pattern. This pattern emphasizes extensibility, maintainability, and Apple-style API design, making it ideal for building reusable component libraries.

Agent Behavior Contract

  1. Before creating a new component, verify it needs 2+ meaningfully different styles. If not, skip the style protocol.
  2. Follow Apple's patterns (
    ButtonStyle
    ,
    LabelStyle
    ) as the gold standard.
  3. Use nested type-erased views in configuration (like
    LabelStyleConfiguration.Title
    ).
  4. Never expose configuration initializers as
    public
    .
  5. Wrap
    style.makeBody
    in
    AnyView
    for type erasure.
  6. Use
    DynamicProperty
    for style protocols, not
    Sendable
    .
  7. Read styles from environment using existential type (
    any [Component]Style
    ).

Core Architecture Principles

  • Protocol:
    [Component]Style
    — defines HOW to render
  • Configuration:
    [Component]StyleConfiguration
    — contains WHAT to render
  • Component: Creates configuration internally, delegates rendering to style
  • Environment: Enables subtree styling without modifying components

When to Use Style Protocol

Use style protocol when:

  • 2+ meaningfully different visual styles needed
  • Environment-based style cascading desired
  • Building reusable component library
  • Styles need environment access (
    @Environment
    )

Skip style protocol when:

  • Single visual representation
  • Simple, static appearance
  • One-off internal component
  • No style customization needed

Quick Decision Tree

Building a new component?

  1. Needs multiple styles? → If no, skip style protocol
  2. Create structure → See
    references/component-structure.md
  3. Define protocol → See
    references/style-protocol.md
  4. Create configuration → Nested type-erased views pattern
  5. Implement default style → Basic rendering
  6. Add environment key → See
    references/environment-keys.md
  7. Add convenience accessors → See
    references/common-patterns.md

Adding a new style?

  1. Create struct conforming to
    [Component]Style
  2. Implement
    makeBody(configuration:)
  3. Add convenience accessor (
    .myStyle
    )
  4. No component changes needed!

Reviewing component architecture?

  1. Check protocol uses
    DynamicProperty
    ,
    @ViewBuilder @MainActor
  2. Verify configuration uses nested type-erased views
  3. Confirm configuration init is
    internal
  4. Check component wraps
    style.makeBody
    in
    AnyView
  5. Validate environment key uses existential (
    any [Component]Style
    )
  6. Ensure new styles don't require component changes

Triage Playbook

  • Creating component file structure
    references/component-structure.md
  • Defining style protocol
    references/style-protocol.md
  • Creating configuration with content
    references/style-protocol.md
  • Setting up environment keys
    references/environment-keys.md
  • Adding convenience initializers
    references/common-patterns.md
  • Parameterized or adaptive styles
    references/common-patterns.md
  • Using design tokens
    references/design-system.md
  • Writing snapshot tests
    references/testing.md
  • Organizing previews
    references/previews.md
  • Accessibility requirements
    references/accessibility.md

Quick Reference

Component Structure

[Component]/
├── EnvironmentKeys/[Component]StyleKey.swift
├── Styles/
│   ├── [Component]Style.swift
│   ├── [Component]StyleConfiguration.swift
│   └── Default[Component]Style.swift
└── [Component].swift

Pattern Summary

  • Style Protocol:
    DynamicProperty
    ,
    @ViewBuilder @MainActor func makeBody
  • Configuration: Nested
    struct Content: View
    with
    private let _body: () -> AnyView
  • Configuration Init:
    internal
    (not
    public
    )
  • Component Body:
    AnyView(style.makeBody(configuration: .init(...)))
  • Environment Key:
    @Entry public var style: any [Component]Style = .automatic
  • View Modifier:
    func style(_ style: some [Component]Style) -> some View

Best Practices

DO

  • Use
    DynamicProperty
    for style protocols
  • Use nested type-erased views in configuration
  • Make configuration initializers
    internal
  • Wrap
    style.makeBody
    in
    AnyView
  • Use design tokens, not magic numbers
  • Provide convenience accessors (
    .compact
    ,
    .outlined
    )

DON'T

  • Add explicit
    Sendable
    to protocols
  • Make configuration initializers
    public
  • Pass configuration to component initializer
  • Put content properties in style protocol
  • Use style protocol for single-variant components
  • Use
    AnyView
    directly as configuration property type

Review Checklist

Architecture

  • Style protocol uses
    DynamicProperty
  • makeBody
    has
    @ViewBuilder @MainActor
  • Configuration uses nested type-erased views
  • Configuration init is
    internal
  • Component wraps
    style.makeBody
    in
    AnyView
  • New styles don't require component changes

Environment

  • Uses
    @Entry
    macro
  • Stores as existential (
    any [Component]Style
    )
  • Modifier uses opaque parameter (
    some [Component]Style
    )
  • Default value provided (
    .automatic
    )

Quality

  • Previews cover all styles (see
    references/previews.md
    )
  • Snapshot tests exist (see
    references/testing.md
    )
  • Accessibility supported (see
    references/accessibility.md
    )
  • Design tokens used (see
    references/design-system.md
    )

Reference Files

  • references/component-structure.md
    — Creating new component, file organization
  • references/style-protocol.md
    — Protocol definition, configuration pattern, adding styles
  • references/environment-keys.md
    — Environment injection, usage patterns
  • references/common-patterns.md
    — Convenience initializers/accessors, parameterized styles
  • references/testing.md
    — Snapshot tests, test organization
  • references/previews.md
    — Preview structure, checklist
  • references/design-system.md
    — Design tokens, typography, colors
  • references/accessibility.md
    — Labels, identifiers, Dynamic Type

Philosophy

Style-driven components prioritize:

  1. Extensibility — Add styles without modifying components
  2. Apple Patterns — Follow
    ButtonStyle
    ,
    LabelStyle
    conventions
  3. Simplicity — Skip style protocol for single-variant components
  4. Type Safety — Generic APIs with internal type erasure
  5. Consistency — All components follow same patterns