Skip to content

Shadcn-Style Components

ZyraForm works beautifully with Shadcn-inspired SwiftUI components. Here are reusable components styled to match Shadcn UI patterns.

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)
}
}
}
}
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
}
}
}
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)
}
}
}
}
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)
}
}

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
}
}
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)
}
}
}
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
}
}
}
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)
}
}
}
}
// Usage
FormField(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)
}
}
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
}
}
}