Kbd
Native kbd composition, package imports, and editable Swift source.
Preview


Use it
Import SwiftCN to use CNKbd directly. This complete example also runs in the native component gallery.
import SwiftUI
import SwiftCN
struct KbdExample: View {
@Environment(\.colorScheme) private var colorScheme
@Environment(\.twTheme) private var theme
@State private var presented = false
@State private var selection: String?
private let options = [
CNOption("button", title: "Button", detail: "Actions and keyboard shortcuts", systemImage: "cursorarrow.click"),
CNOption("calendar", title: "Calendar", detail: "Choose a date", systemImage: "calendar"),
CNOption("resizable", title: "Resizable", detail: "Adjust panel sizes", systemImage: "rectangle.split.2x1")
]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
CNButton(variant: .outline, action: { presented = true }) {
HStack(spacing: 12) {
Text("Search")
CNKbd("⌘ K").accessibilityLabel("Command K")
}
}
.keyboardShortcut("k", modifiers: .command)
.sheet(isPresented: $presented) {
VStack(spacing: 0) {
CNCommand(options, selection: $selection, classes: "h-[230]") { _ in presented = false }
CNSeparator()
HStack(spacing: 6) {
#if os(macOS)
Text("↑ ↓ Navigate ↵ Choose").tw("text-xs text-mutedForeground")
#else
Text("Choose a component").tw("text-xs text-mutedForeground")
#endif
Spacer()
CNDialogClose("Close", classes: "border-0 text-xs font-medium px-2 py-1")
}.tw("px-3 py-2")
}
#if os(macOS)
.tw("w-[380] bg-surface text-foreground")
#else
.tw("w-full bg-surface text-foreground")
.frame(maxHeight: .infinity, alignment: .top)
.presentationDetents([.height(300)])
.presentationDragIndicator(.visible)
#endif
.presentationBackground(theme.color(.surface, scheme: colorScheme))
.preferredColorScheme(colorScheme)
}
Text(selection.flatMap { id in options.first { $0.id == id }?.title }
.map { "Selected: \($0)" } ?? "Click Search or press ⌘ K.")
.tw("text-sm text-mutedForeground")
}
}
}Native behavior
CNKbd displays a keycap with native monospaced font design and editable classes. Place it inside the action label, then attach SwiftUI keyboardShortcut to that action control. The example opens the same compact command palette by click or Command-K. Choosing a result updates the label; Close or Escape dismisses the dialog.
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.
import SwiftUI
import SwiftCN
public struct CNAlertTitle<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("alert-title", classes)) }
}
public struct CNAlertDescription<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("alert-description", classes)) }
}
public struct CNItemTitle<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("item-title", classes)) }
}
public struct CNItemDescription<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("item-description", classes)) }
}
public struct CNEmptyTitle<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("empty-title", classes)) }
}
public struct CNEmptyDescription<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("empty-description", classes)) }
}
public struct CNKbd<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("kbd", classes)).cnTextUtilities() }
}
public struct CNLabel<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("label", classes)) }
}
public struct CNDialogTitle<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("dialog-title", classes)) }
}
public struct CNDialogDescription<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("dialog-description", classes)) }
}
public struct CNMessageMeta<Content: View>: View {
private let classes: TWClasses
private let content: Content
public init(_ classes: TWClasses = "", @ViewBuilder content: () -> Content) {
self.classes = classes; self.content = content()
}
public init(_ title: LocalizedStringKey, classes: TWClasses = "") where Content == Text {
self.classes = classes; content = Text(title)
}
public var body: some View { content.tw(cn("message-meta", classes)) }
}