🪄 The Magic of shadcn/ui, Now in SwiftUI! SwiftCN brings the elegant, customizable design philosophy of shadcn/ui to the SwiftUI ecosystem. Not a rigid component library, but a collection of reusable components you can copy, paste, and customize to your heart's content! Built on a solid foundation of design tokens and best practices, SwiftCN gives you the building blocks for creating beautiful, accessible, and consistent UIs without sacrificing flexibility.
- 🧩 Modular Components: Use what you need, leave what you don't
- 🎨 Beautiful Slate Theme: Sophisticated color palette with dark mode support
- ♿ Accessibility First: Designed with a11y in mind
- 🧪 Swift Package Manager: Easy to integrate into your projects
- 💪 SwiftUI Native: Built for SwiftUI, by SwiftUI enthusiasts
- 🎯 iOS 15+ & macOS 12+: Targeting modern Apple platforms
- 🛠️ Fully Customizable: Tweak and adapt to fit your needs
Add SwiftCN to your project through Xcode:
- Go to File > Add Packages...
- Enter the repository URL:
https://github.com/gillesdm/SwiftCN.git
(Note: Update this URL if it changes) - Choose the version rule (recommended: Up to Next Major)
- Click Add Package
Or add it directly to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/gillesdm/SwiftCN.git", from: "0.1.0") // Note: Update this URL if it changes
]
import SwiftUI
import SwiftCN
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("Welcome to SwiftCN!")
.typography(.h2)
SButton("Get Started", size: .lg, fullWidth: true) {
print("Let's go!")
}
SButton("Learn More",
variant: .outline,
icon: Image(systemName: "book.fill"),
action: {
print("Opening docs...")
})
SButton("Cancel",
variant: .ghost,
size: .sm,
action: {
print("Cancelled")
})
}
.padding()
}
}
The SButton component is the cornerstone of user interaction in any app. SwiftCN's button is highly customizable with various styles, sizes, and states.
More about the SButton
// Primary button (default)
SButton("Primary Button") {
// Action here
}
// Secondary button
SButton("Secondary Button", variant: .secondary) {
// Action here
}
// Outline button
SButton("Outline Button", variant: .outline) {
// Action here
}
// Ghost button
SButton("Ghost Button", variant: .ghost) {
// Action here
}
// Link button
SButton("Link Button", variant: .link) {
// Action here
}
// Destructive button
SButton("Delete", variant: .destructive) {
// Careful now!
}
SButton("Small", size: .sm) {}
SButton("Medium", size: .md) {} // Default
SButton("Large", size: .lg) {}
// Leading icon (default)
SButton("With Icon",
icon: Image(systemName: "star.fill")) {
// Action
}
// Trailing icon
SButton("Next",
icon: Image(systemName: "arrow.right"),
iconPosition: .trailing) {
// Action
}
// Full width button
SButton("Submit", fullWidth: true) {
// Action
}
// Disabled button
SButton("Not Available", isEnabled: false) {
// This action won't trigger
}
A vertically stacked set of interactive headings that each reveal a section of content.
More about SAccordion
import SwiftUI
import SwiftCN
struct AccordionExample: View {
var body: some View {
SAccordion(type: .single, defaultOpenItems: ["item-1"]) {
SAccordionItem(id: "item-1", title: "Is it accessible?") {
Text("Yes. It adheres to the WAI-ARIA design pattern.")
.padding() // Add padding to content
}
SAccordionItem(id: "item-2", title: "Is it styled?") {
Text("Yes. It comes with default styles that matches the other components' aesthetic.")
.padding()
}
SAccordionItem(id: "item-3", title: "Is it animated?") {
Text("Yes. It's animated by default, but you can disable it if you prefer.")
.padding()
}
}
.padding() // Add padding around the accordion
}
}
.single
: Allows only one item to be open at a time..multiple
: Allows multiple items to be open simultaneously (default).
defaultOpenItems
: An array of item IDs that should be open by default.SAccordionItem
:id
: A unique string identifier for the item.title
: The text displayed in the item's header.icon
: An optionalImage
to display next to the title (not implemented in the current version shown).content
: The view to display when the item is open.
The SAlert
component displays important messages in different styles.
More about the SAlert
// Default alert with title and description
SAlert(
title: "Heads up!",
description: "This is an important message."
)
// Destructive alert
SAlert(
title: "Warning",
description: "This action cannot be undone.",
variant: .destructive
)
// Success alert
SAlert(
title: "Success",
description: "Your changes have been saved.",
variant: .success
)
// Default variant
SAlert(title: "Default", description: "Message...", variant: .default_)
// Destructive variant
SAlert(title: "Destructive", description: "Message...", variant: .destructive)
// Success variant
SAlert(title: "Success", description: "Message...", variant: .success)
// Warning variant
SAlert(title: "Warning", description: "Message...", variant: .warning)
SAlert(title: "Custom Content") {
VStack(alignment: .leading, spacing: 8) {
Text("You can add any custom content here.")
SButton("Take Action", size: .sm) {
// Handle action
}
}
}
SAlert(
title: "Custom Icon",
description: "This alert uses a custom icon.",
icon: Image(systemName: "star.fill")
)
The SAlertDialog
component is a modal dialog that appears on top of the entire application.
More about the SAlertDialog
First, add the withAlertDialogs()
modifier to your root view:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.withAlertDialogs()
}
}
}
SAlertDialog { openAction in
SButton("Open Dialog") {
openAction()
}
} content: {
VStack(alignment: .leading, spacing: 0) {
SAlertDialogParts.Title("Are you sure?")
SAlertDialogParts.Description("This action cannot be undone.")
SAlertDialogParts.Footer {
SButton("Cancel", variant: .outline) {
AlertDialogController.shared.dismiss()
}
SButton("Continue") {
// Perform action
AlertDialogController.shared.dismiss()
}
}
}
}
The SAvatar component displays a user's profile picture or a fallback.
More about the SAvatar
// Initials fallback
SAvatar(initials: "JD")
// Icon fallback
SAvatar(systemName: "person.fill")
// With image and initials fallback
SAvatar(image: Image("profile-pic"), initials: "JD")
// With URL image and initials fallback
SAvatar.url(URL(string: "..."), fallbackInitials: "JD")
// Different sizes and shapes
SAvatar(initials: "SM", size: .sm, shape: .rounded)
// With status indicator
SAvatar(initials: "ON", status: .online)
The SBadge
component is used for displaying small status descriptors or tags.
More about the SBadge
SBadge("Default Badge") // Default style
SBadge("Active", variant: .success)
SBadge("Beta", variant: .secondary)
SBadge("Error", variant: .destructive)
SBadge("Archived", variant: .outline)
Variants
- .default_ (Primary background)
- .secondary (Secondary background)
- .destructive (Destructive background)
- .outline (Transparent background with border)
- .success (Success background)
- .warning (Warning background)
- .info (Info background)
The SCard
component is a versatile container for grouping content with a distinct background, border, and padding. It supports structured composition using nested parts.
More about the SCard
SCard {
// Content goes here, usually using SCardParts
Text("Simple card content")
.padding() // Add padding if not using SCardParts.Content
}
Use the nested SCardParts structs for standard card layouts:
SCard {
SCardParts.Header {
SCardParts.Title("Account Settings")
SCardParts.Description("Update your profile information.")
}
SCardParts.Content {
// Your form fields or other content here
Text("Form content goes here...")
.foregroundColor(Colors.cardForeground)
}
Divider().padding(.horizontal, Spacing.lg) // Optional Divider
SCardParts.Footer {
HStack {
Spacer()
SButton("Save Changes") {}
}
}
}
- SCardParts.Header: Top section, typically contains Title and Description. Applies standard header padding.
- SCardParts.Title: Styled text for the main card title within the Header.
- SCardParts.Description: Styled text for secondary information within the Header.
- SCardParts.Content: Main content area. Applies standard content padding.
- SCardParts.Footer: Bottom section, often for actions. Applies standard footer padding.
Note: Padding is applied by the Header, Content, and Footer parts. If you place content directly in SCard without these parts, you may need to add your own padding.
SwiftCN is built on a token-based design system, making it incredibly customizable:
// Use the default slate theme
// Or customize with your own colors in Assets.xcassets
// Typography
Text("Heading").typography(.h1)
Text("Body text").typography(.base)
// Spacing
.padding(Spacing.md)
.padding(.horizontal, Spacing.lg)
SwiftCN is just getting started! Here's what's coming:
- Accordion
- More base components (Card, Input, Checkbox, Toggle)
- Compound components (Form, Dialog, Dropdown)
- Animation and transition presets
- More themes beyond Slate
- SwiftUI Previews Catalog
- Documentation site with examples
- iOS and macOS example apps
We'd love your help making SwiftCN even better!
- Fork the repository
- Create a new branch (
git checkout -b feature/amazing-component
) - Make your changes
- Run tests (
swift test
) - Commit your changes (
git commit -m 'Add amazing component'
) - Push to the branch (
git push origin feature/amazing-component
) - Open a Pull Request
- Follow Swift style guidelines
- Include previews for visual components
- Write tests for new functionality
- Update documentation with new components
SwiftCN is available under the MIT license. See the LICENSE
file for more information.
- Inspired by shadcn/ui
- Built with SwiftUI
- Thanks to the open-source community
🧙♂️ "Great UIs are indistinguishable from magic. SwiftCN is your spellbook."