Shadcn-Style Components
ZyraForm works beautifully with Shadcn-inspired SwiftUI components. Here are reusable components styled to match Shadcn UI patterns.
Form Components
Section titled “Form Components”Input Component
Section titled “Input Component”import SwiftUI
struct Input: View { let label: String @Binding var text: String let placeholder: String var error: String? = nil var keyboardType: UIKeyboardType = .default
var body: some View { VStack(alignment: .leading, spacing: 8) { Text(label) .font(.caption) .foregroundColor(.secondary)
TextField(placeholder, text: $text) .keyboardType(keyboardType) .textFieldStyle(.roundedBorder) .padding(.horizontal, 12) .padding(.vertical, 8) .background(Color(.systemBackground)) .overlay( RoundedRectangle(cornerRadius: 6) .stroke(error != nil ? Color.red : Color.gray.opacity(0.2), lineWidth: 1) )
if let error = error { Text(error) .font(.caption) .foregroundColor(.red) .padding(.horizontal, 4) } } }}Button Component
Section titled “Button Component”struct Button: View { let title: String let action: () -> Void var variant: ButtonVariant = .default var disabled: Bool = false
enum ButtonVariant { case `default` case destructive case outline case ghost }
var body: some View { SwiftUI.Button(action: action) { Text(title) .frame(maxWidth: .infinity) .padding(.vertical, 10) .background(backgroundColor) .foregroundColor(foregroundColor) .cornerRadius(6) } .disabled(disabled) }
private var backgroundColor: Color { switch variant { case .default: return disabled ? Color.gray.opacity(0.3) : Color.blue case .destructive: return disabled ? Color.gray.opacity(0.3) : Color.red case .outline: return Color.clear case .ghost: return Color.clear } }
private var foregroundColor: Color { switch variant { case .default, .destructive: return disabled ? Color.gray : Color.white case .outline, .ghost: return disabled ? Color.gray : Color.primary } }}Label Component
Section titled “Label Component”struct Label: View { let text: String var required: Bool = false
var body: some View { HStack(spacing: 4) { Text(text) .font(.caption) .foregroundColor(.secondary) if required { Text("*") .foregroundColor(.red) } } }}Card Component
Section titled “Card Component”struct Card<Content: View>: View { let content: Content
init(@ViewBuilder content: () -> Content) { self.content = content() }
var body: some View { VStack(alignment: .leading, spacing: 16) { content } .padding(16) .background(Color(.systemBackground)) .cornerRadius(8) .shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2) }}Form Integration
Section titled “Form Integration”Complete Form with Shadcn-Style Components
Section titled “Complete Form with Shadcn-Style Components”struct UserFormView: View { @StateObject var form = PowerSyncForm<UserFormValues>(schema: UsersSchema)
var body: some View { ScrollView { VStack(spacing: 24) { Card { VStack(alignment: .leading, spacing: 20) { Text("Account Information") .font(.headline)
if let emailBinding = form.binding(for: "email") { Input( label: "Email", text: emailBinding, placeholder: "Enter your email", error: form.getError("email"), keyboardType: .emailAddress ) }
if let nameBinding = form.binding(for: "name") { Input( label: "Name", text: nameBinding, placeholder: "Enter your name", error: form.getError("name") ) }
if let ageBinding = form.intBinding(for: "age") { Input( label: "Age", text: ageBinding, placeholder: "Enter your age", error: form.getError("age"), keyboardType: .numberPad ) } } } .padding(.horizontal, 16)
VStack(spacing: 12) { Button( title: "Submit", action: { form.handleSubmit { values in Task { await saveUser(values) } } }, variant: .default, disabled: !form.isValid || form.isSubmitting )
Button( title: "Reset", action: { form.reset() }, variant: .outline ) } .padding(.horizontal, 16) } .padding(.vertical, 20) } .navigationTitle("Registration") }
private func saveUser(_ values: UserFormValues) async { // Save logic }}Alert Component
Section titled “Alert Component”struct Alert: View { let title: String let message: String var variant: AlertVariant = .default
enum AlertVariant { case `default` case success case error case warning }
var body: some View { HStack(spacing: 12) { Image(systemName: iconName) .foregroundColor(iconColor)
VStack(alignment: .leading, spacing: 4) { Text(title) .font(.headline) Text(message) .font(.caption) .foregroundColor(.secondary) }
Spacer() } .padding(12) .background(backgroundColor) .cornerRadius(6) .overlay( RoundedRectangle(cornerRadius: 6) .stroke(borderColor, lineWidth: 1) ) }
private var iconName: String { switch variant { case .default: return "info.circle" case .success: return "checkmark.circle" case .error: return "xmark.circle" case .warning: return "exclamationmark.triangle" } }
private var iconColor: Color { switch variant { case .default: return .blue case .success: return .green case .error: return .red case .warning: return .orange } }
private var backgroundColor: Color { switch variant { case .default: return Color.blue.opacity(0.1) case .success: return Color.green.opacity(0.1) case .error: return Color.red.opacity(0.1) case .warning: return Color.orange.opacity(0.1) } }
private var borderColor: Color { switch variant { case .default: return Color.blue.opacity(0.3) case .success: return Color.green.opacity(0.3) case .error: return Color.red.opacity(0.3) case .warning: return Color.orange.opacity(0.3) } }}Badge Component
Section titled “Badge Component”struct Badge: View { let text: String var variant: BadgeVariant = .default
enum BadgeVariant { case `default` case secondary case success case error case warning }
var body: some View { Text(text) .font(.caption) .fontWeight(.medium) .padding(.horizontal, 8) .padding(.vertical, 4) .background(backgroundColor) .foregroundColor(foregroundColor) .cornerRadius(12) }
private var backgroundColor: Color { switch variant { case .default: return Color.blue.opacity(0.1) case .secondary: return Color.gray.opacity(0.1) case .success: return Color.green.opacity(0.1) case .error: return Color.red.opacity(0.1) case .warning: return Color.orange.opacity(0.1) } }
private var foregroundColor: Color { switch variant { case .default: return .blue case .secondary: return .gray case .success: return .green case .error: return .red case .warning: return .orange } }}Form with Error Display
Section titled “Form with Error Display”struct FormField<Content: View>: View { let label: String let error: String? var required: Bool = false let content: () -> Content
var body: some View { VStack(alignment: .leading, spacing: 8) { Label(text: label, required: required)
content() .overlay( RoundedRectangle(cornerRadius: 6) .stroke(error != nil ? Color.red : Color.gray.opacity(0.2), lineWidth: 1) )
if let error = error { HStack(spacing: 4) { Image(systemName: "exclamationmark.circle.fill") .font(.caption) .foregroundColor(.red) Text(error) .font(.caption) .foregroundColor(.red) } .padding(.horizontal, 4) } } }}
// UsageFormField(label: "Email", error: form.getError("email"), required: true) { if let binding = form.binding(for: "email") { TextField("Enter email", text: binding) .padding(.horizontal, 12) .padding(.vertical, 8) }}Complete Example
Section titled “Complete Example”struct ModernUserFormView: View { @StateObject var form = PowerSyncForm<UserFormValues>(schema: UsersSchema) @State private var showSuccess = false
var body: some View { ScrollView { VStack(spacing: 24) { if showSuccess { Alert( title: "Success", message: "User created successfully", variant: .success ) .padding(.horizontal, 16) }
Card { VStack(alignment: .leading, spacing: 20) { HStack { Text("Create Account") .font(.title2) .fontWeight(.bold) Spacer() Badge(text: "Required", variant: .warning) }
FormField(label: "Email", error: form.getError("email"), required: true) { if let binding = form.binding(for: "email") { TextField("Enter email", text: binding) .textContentType(.emailAddress) .autocapitalization(.none) .padding(.horizontal, 12) .padding(.vertical, 8) } }
FormField(label: "Name", error: form.getError("name"), required: true) { if let binding = form.binding(for: "name") { TextField("Enter name", text: binding) .padding(.horizontal, 12) .padding(.vertical, 8) } } } } .padding(.horizontal, 16)
VStack(spacing: 12) { Button( title: form.isSubmitting ? "Submitting..." : "Create Account", action: { form.handleSubmit { values in Task { await saveUser(values) } } }, variant: .default, disabled: !form.isValid || form.isSubmitting )
Button( title: "Reset Form", action: { form.reset() showSuccess = false }, variant: .outline ) } .padding(.horizontal, 16) } .padding(.vertical, 20) } .navigationTitle("Registration") }
private func saveUser(_ values: UserFormValues) async { // Save logic await MainActor.run { showSuccess = true } }}Next Steps
Section titled “Next Steps”- Form Components - Learn more about form bindings
- Validation - Add validation to your forms
- CRUD Operations - Save form data