swiftcnearly preview
Component library

Item

Native item composition, package imports, and editable Swift source.

Preview

Item rendered by native SwiftUI.Item rendered by native SwiftUI. Dark appearance.
Captured from the native SwiftUI catalog on macOS.

Use it

Import SwiftCN to use CNItem directly. This complete example also runs in the native component gallery.

ItemExample.swift
import SwiftUI
import SwiftCN

struct ItemExample: View {

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            CNItem {
                Image(systemName: "folder").accessibilityHidden(true)
                CNItemContent { CNItemTitle("Design system"); CNItemDescription("Shared native components") }
                Spacer()
                CNItemActions { CNButton("Open", variant: .outline, action: {}) }
            }

        }
    }
}

Native behavior

Compose leading media, CNItemContent, title, description, and CNItemActions in a native row. The row aligns content at its center. Place interactive content in native controls to define activation semantics. CNItemActions keeps the same controls in a row or a vertical stack, based on their natural widths and the available space. It uses the shared native adaptive action layout.

Style and compose

Visual recipes use .tw classes. Append classes: to override a recipe locally, or change the recipe in TWGlobalRules.named for your subtree. Native layout and interaction modifiers remain SwiftUI APIs. For control colors, append cn-tint-[primary], another theme token, or an interpolated Color.

Motion recipes are editable too: control-motion uses a 150 ms smooth response, disclosure-motion uses a 250 ms spring, and feedback-motion uses a 200 ms smooth response. Set a recipe to animate-none in your global rules to turn it off. Presets respect Reduce Motion. Static components stay still; native scrolling, drag tracking, and platform presentation keep their own gestures.

Action groups and card, dialog, and questionnaire footers stack when their content does not fit. The native layout preserves the same child controls during this change.

View builders preserve native child content. Native Button roles, labels, keyboard shortcuts, submit actions, and presentation modifiers still work. A string recipe does not replace a native control API or a typed chart mark.

Own the source

Copy the complete file below into your app alongside import SwiftCN. The file contains related composable parts from the same family. Its dependencies use public package APIs, so copied and imported components can coexist.

To own the styling engine too, download the complete source bundle. It contains every component, the .tw engine, its recipes, and the MIT license. Keep those files in one module and remove import SwiftCN from the usage example.

ComponentContainers.swift
import SwiftUI
import SwiftCN

public struct CNAlert<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 8, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.frame(maxWidth: .infinity, alignment: .leading).tw(cn("alert", classes))
    }
}

public struct CNItem<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 12, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        HStack(alignment: .center, spacing: spacing) { content }.frame(maxWidth: .infinity, alignment: .leading).tw(cn("item", classes))
    }
}

public struct CNItemContent<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 4, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.tw(cn("", classes))
    }
}

public struct CNItemActions<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 8, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        CNAdaptiveActionLayout(spacing: spacing) { content }.tw(classes)
    }
}

public struct CNEmpty<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 12, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .center, spacing: spacing) { content }.tw(cn("empty", classes))
    }
}

public struct CNEmptyContent<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 8, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.tw(cn("", classes))
    }
}

public struct CNDialogContent<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 12, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.frame(maxWidth: .infinity, alignment: .leading).tw(cn("dialog", classes))
    }
}

public struct CNDialogHeader<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 6, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.tw(cn("", classes))
    }
}

public struct CNDialogFooter<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 8, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        CNAdaptiveActionLayout(spacing: spacing, verticalAlignment: .trailing) { content }.frame(maxWidth: .infinity, alignment: .trailing).tw(cn("dialog-footer", classes))
    }
}

public struct CNBubble<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 8, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.tw(cn("bubble", classes))
    }
}

public struct CNBubbleGroup<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 4, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.tw(cn("", classes))
    }
}

public struct CNMessage<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 12, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        HStack(alignment: .top, spacing: spacing) { content }.tw(cn("message", classes))
    }
}

public struct CNMessageContent<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 6, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        VStack(alignment: .leading, spacing: spacing) { content }.tw(cn("", classes))
    }
}

public struct CNMessageActions<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 6, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        CNAdaptiveActionLayout(spacing: spacing) { content }.tw(classes)
    }
}

public struct CNInputGroupAddon<Content: View>: View {
    private let classes: TWClasses
    private let spacing: CGFloat
    private let content: Content
    public init(_ classes: TWClasses = "", spacing: CGFloat = 6, @ViewBuilder content: () -> Content) {
        self.classes = classes; self.spacing = spacing; self.content = content()
    }
    public var body: some View {
        HStack(alignment: .center, spacing: spacing) { content }.tw(cn("text-mutedForeground", classes))
    }
}

On this page