A set of SwiftUI components inspired by the NeoBrutalism design trend.
This started as a learning project for SwiftUI but grew into a reusable UI library. It's useful for anyone looking to build apps with a bold, minimal style.
Feel free to contribute by reporting bugs or submitting fixes.
- Mismatch
- More coming soon...
You can add NeoBrutalism to your Swift project using Swift Package Manager.
- In Xcode, go to File -> Swift Packages -> Add Package Dependency.
- Enter the repository URL: https://github.com/rational-kunal/NeoBrutalism.git
- Choose the version or branch you want to use.
NeoBrutalism offers a variety of components that can be seamlessly integrated into your SwiftUI project.
import NeoBrutalism
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
NBTheme.default.background
.ignoresSafeArea()
Toggle(isOn: .constant(true)) { Text("Are you a wizard?") }
.toggleStyle(.neoBrutalismChecklist)
}
}
}NeoBrutalism supports theming, with both light and dark mode options. You can customize or create your own themes. To apply a theme to a view, use the nbTheme() modifier.
struct ContentView: View {
var theme = NBTheme.default.updateBy(background: .black, mainText: .white)
var body: some View {
ZStack {
theme.background
.ignoresSafeArea()
Toggle(isOn: .constant(true)) { Text("Are you a wizard?") }
.toggleStyle(.neoBrutalismChecklist)
}.nbTheme(theme)
}
}NeoBrutalism includes commonly used UI components, with plans to expand as needed. Feel free to contribute!
Toggle(isOn: $checkboxState) { Text(checkboxState ? "(Alohomora!)" : "(Colloportus!)") }
.toggleStyle(.neoBrutalismChecklist)Toggle(isOn: $switchState) { Text(switchState ? "(Lumos!)" : "(Nox!)") }
.toggleStyle(.neoBrutalismSwitch)
DisclosureGroup("Expecto Patronum") {
Text("Pitradev Sanrakshanam - पितृदेव संरक्षणम्")
}.disclosureGroupStyle(.neoBrutalismAccordion)Button {
counter += 1
} label: {
Text("Accio")
}.buttonStyle(.neoBrutalism())
Button {
counter += 1
} label: {
Image(systemName: "wand.and.sparkles.inverse")
.bold()
}.buttonStyle(.neoBrutalism(type: .neutral, variant: .reverse))NBCard {
Text("Hogwarts Letter")
} main: {
Text("You have been accepted to Hogwarts School of Witchcraft and Wizardry!")
} footer: {
Button {
// No-op
} label: {
Text("Open Letter").frame(maxWidth: .infinity)
}.buttonStyle(.neoBrutalism())
}
NBCard(type: .neutral) {
Text("Quidditch Gear")
} main: {
Text("Get your broomstick, Quidditch robes, and golden snitch!")
} footer: {
HStack(spacing: 12.0) {
Button {
// No-op
} label: {
Text("Open Firebolt")
}.buttonStyle(.neoBrutalism(type: .neutral))
Spacer()
Button {
// No-op
} label: {
Text("Snitch")
}.buttonStyle(.neoBrutalism())
}
}TextField("Enter your spell", text: $text)
.textFieldStyle(.neoBrutalism)ProgressView(value: 0.7)
.progressViewStyle(.neoBrutalism)
struct SliderExampleView: View {
@State var sliderValue: CGFloat = 0.0
var body: some View {
HStack {
Text("\(sliderValue, specifier: "%.2f")")
.frame(width: 50.0, alignment: .leading)
NBSlider(value: $sliderValue)
}
}
}struct RadioGroupExampleView: View {
@State private var selectedSpell: Int = 0
var body: some View {
VStack(alignment: .leading, spacing: 8.0) {
Text("Selected Spell: \(selectedSpell)")
.font(.title3)
NBRadioGroup(value: $selectedSpell) {
VStack(alignment: .leading) {
NBRadioItem(value: 0) {
Text("Expelliarmus")
}
NBRadioItem(value: 1) {
Text("Lumos")
}
NBRadioItem(value: 2) {
Text("Wingardium Leviosa")
}
}.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
}struct TabsExampleView: View {
@State private var selectedTab: Int = 0
var body: some View {
VStack(spacing: 8.0) {
Text("House Selection: \(selectedTab)")
.font(.title3)
NBTabs(selectedTabItem: $selectedTab) {
NBTabsList {
NBTabsTrigger(tabItem: 0) { Image(systemName: "flame.fill") }
NBTabsTrigger(tabItem: 1) { Image(systemName: "lanyardcard.fill") }
NBTabsTrigger(tabItem: 2) { Image(systemName: "book.fill") }
NBTabsTrigger(tabItem: 3) { Image(systemName: "leaf.fill") }
}
NBFlatCard {
ZStack {
NBTabsContent(tabItem: 0) { Text("Bravery and Daring!") }
NBTabsContent(tabItem: 1) { Text("Cunning and Ambition!") }
NBTabsContent(tabItem: 2) { Text("Wisdom and Learning!") }
NBTabsContent(tabItem: 3) { Text("Loyalty and Hard Work!") }
}
}
}
}
.padding()
}
}NBCollapsable(isExpanded: $isExpanded) {
NBFlatCard {
HStack {
Text("Need something?")
Spacer()
NBCollapsibleTrigger {
Image(systemName: isExpanded ? "door.left.hand.open" : "door.left.hand.closed")
}
}
}
NBCollapsableContent {
NBFlatCard(type: .neutral) {
Text("Here’s what you need!")
}
}
}struct DrawerExampleView: View {
@State private var isDrawerOpen: Bool = false
var body: some View {
VStack(spacing: 16.0) {
NBButton {
Text("Open the Chamber")
} action: {
isDrawerOpen.toggle()
}
}
.nbDrawer(isPresented: $isDrawerOpen) {
VStack(spacing: 16) {
Text("Parseltongue Required")
.font(.title2)
Text("Only those who can speak to snakes may proceed.")
.padding(.horizontal, 4.0)
NBButton {
Text("I Understand")
} action: {
isDrawerOpen.toggle()
}
}
}
}
}NBFlatCard {
Text("Quidditch Tryouts - This Saturday!")
}
NBFlatCard(type: .neutral) {
Text("O.W.L. Exams Approaching - Study Hard!")
}
NBAlert {
Text("The Chamber of Secrets has been opened. Enemies of the heir, beware!")
} icon: {
Image(systemName: "exclamationmark.triangle")
} head: {
Text("Warning")
}
NBAlert(type: .neutral) {
Text("Dementors are nearby. Expecto Patronum!")
} head: {
Text("Caution")
}NBBadge {
Text("Gryffindor")
.font(.title3)
}
NBBadge(type: .neutral) {
Text("Slytherin")
}NBFlatCard {
NBRoundSkeleton()
}
.frame(width: 120, height: 120)VStack(alignment: .leading, spacing: 12.0) {
NBTextSkeleton()
NBTextSkeleton()
.frame(width: 120)
}The credit for the design belongs to https://www.neobrutalism.dev.