Radix-UI is an open source library optimized for fast development, easy maintenance, and accessibility.
Since Radix-UI developers said it is not available to Mobile Development (like SwiftUI) and never be (reference), I decided to make it ready for myself to use it because I just like it so much. My inspiration for doing it is Basics website and the minimal and gorgeous design of Radix-UI.
- In Xcode, go to your Package Dependencies
- Put
https://github.com/amirsaam/Radix-UI-Swift.git
in Searchbar - Change
Dependency Rule
toUp to Next Minor Version
- Set the lowest version to your desired version
- Press the
Add Package
button
Since RadixUI for Swift will not have any breaking Semantic's Minor version, it is versioned as Epoch.Major.Minor * 100 + Patch
if it could've, it should've been vice versa as Epoch * 100 + Major
, as result to get the correct behaviour as Semantic Versioning, choosing the Up to Next Minor Version
is the way to go.
Import the package in any place you want to use it as:
import RadixUI
You can use Radix Colors in 2 ways:
Text("RadixUI-Swift")
.foregroundColor(.crimson9) // Directly
or
private var color: RadixAutoColor = .crimson
Text("RadixUI-Swift")
.foregroundColor(color.solid9) // From RadixAutoColor Enum
In order to use your own custom color pallete head to Radix Pallete Generator and create your pallete and add them to your Asset Catalogue in 12 level named in this way for example: MyColor1, MyColor2, MyColor3, MyColor4, MyColor5, ... MyColor12 then using RadixAutoColor's .custom
property:
private var color: RadixAutoColor = .custom("myColor")
// Reads all 12 levels of your custom pallete using the color's child variables.
// It will automatically capitalize the first letter for you.
Text("RadixUI-Swift")
.foregroundColor(color.solid9)
Important Note: All RadixUI modifiers and styles are using RadixAutoColor
enum
Most Important Note: All Radix theme styles have a default dynamic Black on Light ColorScheme and White on Dark ColorScheme, so
DO NOT PASS .whiteA
or .blackA
TO RADIX MODIFIERS
Use icons in Image
with bundle name completely customizable in two ways
// Default SwiftUI's Image
Image("github-logo", bundle: .radixUI)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: anySize, height: anySize)
.foregroundColor(.ruby9)
// Built-in Custom Extension
ResizableBundledImage(imageName: "vercel-logo", imageSize: 20, bundle: .radixUI)
.foregroundColor(.ruby9)
For using in Label
use the built-in extension that uses ResizableBundledImage
:
Label("Vercel", imageName: "vercel-logo", imageSize: 20, bundle: .radixUI)
To create an Radix-themed Avatar loader you can use this View that loads the image from url using built-in SwiftUI's AsyncImage
:
// for person's name abbreviation placeholder/fallback, it will automatically abbreviate the full name
RadixAvatar(
url: URL(string: "urlToImage"),
name: "Passed Name",
variant: .soft,
size: .small,
radius: .large,
color: .grass
)
// or for person icon placeholder/fallback
RadixAvatar(
url: URL(string: "urlToImage"),
variant: .solid,
size: .medium,
radius: .none,
color: .grass
)
// variant: soft, solid
// size: small, medium, large
// radius: none, large, full
You can tranform any Text into a RadixUI badge:
Text("New")
.font(.footnote)
.radixBadge(variant: .outline, radius: .none, color: .grass)
// variant: outline, soft, solid, surface
// radius: none, large, full
RadixUI does have multiple ButtonStyles as: radixGhost, radixOutline, radixSoft, radixSolid and radixSurface
Note 1: RadixUI buttons can have a loading state after press that does change the Button label's icon into a progress view until the boolean becomes false
again.
Examples:
@State private var isLoading = false
// Surely you can go for < Button {} label: {} > approach too!
Button(action: functionName) {
// Best for using Label with RadixUI icons
Label(LocalizedStringKey, imageName: String, imageSize: CGFloat, bundle: Bundle)
// or create one
Label {
Text(LocalizedStringKey)
} icon: {
Image(String)
Image(systemName: String)
Image(String, bundle: Bundle?)
}
// or to use Asset Catalog Image and SFSymbols
Label(LocalizedStringKey, image: String)
Label(LocalizedStringKey, systemImage: String)
}
.buttonStyle(
// for having the similarity to RadixUI buttons like scale and opacity and etc;
// does not change anything of the button but adds some effects.
.radixCustom()
// or
.radixGhost(
layout: .leading,
radius: .large,
color: .grass
),
isLoading: $isLoading
// or
.radixOutline(
layout: .leading,
radius: .large,
color: .grass
),
isLoading: $isLoading
// or
.radixSoft(
layout: .leading,
radius: .large,
color: .grass
),
isLoading: $isLoading
// or
.radixSolid(
layout: .leading,
radius: .large,
color: .grass
),
isLoading: $isLoading
// or
.radixSurface(
layout: .leading,
radius: .large,
color: .grass
),
isLoading: .constant(false) // for disabling the loading state change
)
// layout: icon, title, leading, trailing
// radius: none, large, full
Note 2: Always pass a Label() as label of the button, do not pass Text or Image only, if you want only one of them use the layout
variable's .icon
or .title
cases.
You can tranform any Text into a RadixUI callout:
let infoText = "You will need admin privileges to install and access this application."
let alertText = "Access denied. Please contact the network administrator to view this page."
Text(infoText)
.radixInfoCallout(variant: .surface, color: .grass) // info callout, customizable color, `info-circled` icon
Text(alertText)
.radixAlertCallout(variant: .surface) // alert callout, red color, `exclamation-triangle` icon
// variant: outline, soft, surface
Under the hood it is just a looped Toggle but, it only allows one toggle to be on in the same time, needs to get passed an array of options that their model conforms to certain protocols
// the radio option data options should conform to these protocols
struct Option: Identifiable, Equatable {
let id = UUID()
let title: LocalizedStringKey
}
@State private var selectedOption: Option? = nil
private let options = [
Option(title: "Option 1"),
Option(title: "Option 2"),
Option(title: "Option 3")
]
RadixRadioGroup(
options: options, // pass the array of data options
selected: $selectedOption, // bound of selected option
style: (variant: .surface, layout: .trailing, color: .grass)
) {
// what the options should show as label
Text($0.title)
.foregroundStyle(.grass12)
}
// variant: .soft, surface
// layout: .leading, .trailing
For customizing Segmented Picker of SwiftUI to match Radix style create an init
in @main
struct of the app to apply it globally or just apply such init
in any view you want:
struct PickerDemoView: View {
init() {
RadixSegmentedPicker(
color: .grass,
selectedFont: .systemFont(ofSize: 15, weight: .semibold),
notSelectedFont: .systemFont(ofSize: 15, weight: .light)
)
}
@State private var selected: RadixToggleType = .switch
var body: some View {
Picker(String(""), selection: $selected) {
Text("Checkbox").tag(RadixToggleType.checkbox)
Text("Radio").tag(RadixToggleType.radio)
Text("Switch").tag(RadixToggleType.switch)
Text("Toggle").tag(RadixToggleType.toggle)
}
.pickerStyle(.segmented)
.padding()
.frame(width: 400)
}
}
Important Note: It does not work in macOS yet!
Since SwiftUI's Slider does not accept styles like how button does, RadixUI for Swift has it's own RadixSlider View:
@State private var value = 5.0
RadixSlider(value: $value, step: 1, in: 0...10)
.rxSliderStyle(
.radixSoft(
radius: .full,
size: .medium,
color: .grass
)
// or
.radixSurface(
radius: .full,
size: .medium,
color: .grass
)
)
// radius: .none, .large, .full
// size: small, medium, large
RadixUI for Swift TextFieldStyle just like ButtonStyle has a loading state that transforms iconLabel
into a ProgressView also can have a action passing a function into it with or without a button that will show iconButton
in trailing side that follows textfieldstyle's styling.
@State private var input = ""
@State private var isLoading = false
TextField("Placeholder1", text: $input)
.textFieldStyle(
.radixSurface(
radius: .large,
color: .grass, // optional, default .blue
iconLabel: Image("quote", bundle: .radixUI), // optional
iconButton: Image("arrow-right", bundle: .radixUI), // optional
action: functionName // optional action, does provide .onSubmit by default to textfield
),
isLoading: $isLoading
// or
.radixSoft(
radius: .large,
color: .grass, // optional, default .blue
iconLabel: Image("quote", bundle: .radixUI), // optional image
iconButton: Image("arrow-right", bundle: .radixUI), // optional image
action: functionName // optional action, does provide .onSubmit by default to textfield
),
isLoading: $isLoading
)
// radius: .none, .large, .full
This package has a toast or in-app notification functionality with RadixUI style following Apple Design Guidelines. It does get triggered by a Binding<Bool>
variable, can have action button as a CTA or just a dismiss button, can be swiped to top or bot based on toast showing location.
@State private var presentInfoToast = false
@State private var presentActionToast = false
var body: some View {
VStack { // << this is the highest container in the view, toast should applied to it
HStack {
}
}
.radixInfoToast( // this is a info toast, action is dismiss
$presentInfoToast,
variant: .surface,
position: .bottom,
color: .grass,
duration: 3 // 0 == never, any other number auto dismisses the toast after the given number in seconds
) {
Label(
"This is a Radix Info Toast",
imageName: "vercel-logo",
imageSize: 20,
bundle: .module
) // << pass a label to this part just like how explained in button part of this readme
}
.radixActionToast( // this is a action toast, you should define an action for it
$presentActionToast,
variant: .soft,
position: .top,
color: .grass,
duration: 0
) {
presentInfoToast = true // define the action you wnat here or just pass a function
} buttonLabel: {
Label(
"Ignored Text",
imageName: "avatar",
imageSize: 20,
bundle: .radixUI
) // pass a label as before for the button of the action, only shows the icon
} toastLabel: {
Label(
"Button Presents Info Toast",
imageName: "vercel-logo",
imageSize: 20,
bundle: .radixUI
) // pass a label as the toast's main content
}
}
// variant: .soft, .surface
// position: .bottom, .top
RadixUI has 4 types of toggle styles, one of them is radixRadio
that has been skipped in this part because its mainly used in RadixRadioGroup
and using it as a togglestyle on one item is not a good approach
Toggle(isOn: $toggleBinding) {
ResizableBundledImage( // showed after toggle (checkbox( has been turned on (checked)
imageName: "check",
imageSize: 20,
bundle: .radixUI
)
}
.toggleStyle(
.radixCheckbox(
variant: .surface,
color: .grass
)
)
Toggle(isOn: $toggleBinding) {
Text("Label Text")
.foregroundStyle(radixColor.grass.text2)
}
.toggleStyle(
.radixSwitch( // a switch style, just like swiftui's default with styling of radix ui
variant: .surface,
radius: .full,
color: .grass
)
)
Toggle(isOn: $toggleBinding) {
ResizableBundledImage(
imageName: "font-italic",
imageSize: 20,
bundle: .radixUI
)
}
.toggleStyle(
.radixToggle( // toggle as toggle, dimmer color when off, strong color when on
color: .grass
)
)
// variant: .soft, .surface
// radius: .none, .large, .full
Radix Shadows are available as ViewModifier
in 6 level:
AnyView
.radixShadow1()
.radixShadow2()
.radixShadow3()
.radixShadow4()
.radixShadow5()
.radixShadow6()
Main:
- Add Radix Colors
- Add Radix Icons
- Complete Radix Themes (WIP, surely all of them are not suitable)
Help needed on these, any PR is welcomed:
- Add Ranged Slider (Two Thumbs)
- Add
classic
variant for components that have it - Add Progress Bar
- Add Skeleton
- Improve Shadows
- RadixUI-Swift has no dependency
Amir Mohammadi – @amirsaam – amirsaam [at] me [dot] com
Distributed under the MIT license. See LICENSE
for more information.