-
Notifications
You must be signed in to change notification settings - Fork 0
Text Fields and Inputs
DesignFoundation ships seven input primitives — DFTextField, DFSecureField, DFToggle, DFSlider, DFPicker, DFDatePicker, and DFCheckbox — each following the same style protocol pattern that governs the rest of the library. Every component reads the active theme from the environment, responds to isEnabled, and exposes a dfXxxStyle(_:) modifier for swapping visual variants.
Related pages: Getting-Started · Theming · Buttons · Forms-and-Validation
DFTextField is the standard single-line text input. It supports an optional label, a placeholder, leading and trailing accessory views, and first-class integration with DFValidationState.
// 1. Plain
public init(
_ label: String,
text: Binding<String>,
placeholder: String = "",
validationState: DFValidationState = .none
)
// 2. Leading + trailing accessory views
public init<Leading: View, Trailing: View>(
_ label: String,
text: Binding<String>,
placeholder: String = "",
validationState: DFValidationState = .none,
@ViewBuilder leading: () -> Leading,
@ViewBuilder trailing: () -> Trailing
)
// 3. Leading accessory view only
public init<Leading: View>(
_ label: String,
text: Binding<String>,
placeholder: String = "",
validationState: DFValidationState = .none,
@ViewBuilder leading: () -> Leading
)
// 4. Trailing accessory view only
public init<Trailing: View>(
_ label: String,
text: Binding<String>,
placeholder: String = "",
validationState: DFValidationState = .none,
@ViewBuilder trailing: () -> Trailing
)| Style | Token | Default |
|---|---|---|
DFOutlinedTextFieldStyle |
.outlined |
Yes |
DFFilledTextFieldStyle |
.filled |
No |
DFGlassTextFieldStyle |
.glass |
No — requires iOS 26+ / macOS 26+ |
// Applied to a container — all child fields inherit
VStack {
DFTextField("First name", text: $first)
DFTextField("Last name", text: $last)
}
.dfTextFieldStyle(.outlined)// Minimal
DFTextField("Username", text: $username)
// With placeholder and leading icon
DFTextField("Email address", text: $email, placeholder: "you@example.com") {
Image(systemName: "envelope")
}
// Email field with leading icon and trailing clear button
DFTextField(
"Email",
text: $email,
placeholder: "you@example.com"
) {
Image(systemName: "envelope")
} trailing: {
if !email.isEmpty {
Button { email = "" } label: {
Image(systemName: "xmark.circle.fill")
}
.buttonStyle(.plain)
}
}
// With live validation state
DFTextField(
"Email",
text: $email,
placeholder: "you@example.com",
validationState: emailValidationState
) {
Image(systemName: "envelope")
}
// Filled style
VStack(spacing: 16) {
DFTextField("Display name", text: $displayName)
DFTextField("Handle", text: $handle, placeholder: "@username")
}
.dfTextFieldStyle(.filled)DFSecureField wraps SwiftUI's SecureField and adds a built-in show/hide toggle button on the trailing edge.
public init(
_ label: String,
text: Binding<String>,
placeholder: String = "",
validationState: DFValidationState = .none
)| Style | Token | Default |
|---|---|---|
DFOutlinedSecureFieldStyle |
.outlined |
Yes |
DFFilledSecureFieldStyle |
.filled |
No |
DFGlassSecureFieldStyle |
.glass |
No — requires iOS 26+ / macOS 26+ |
// Minimal password entry
DFSecureField("Password", text: $password, placeholder: "Enter password")
// Confirm password with match validation
DFSecureField(
"Confirm password",
text: $confirmPassword,
placeholder: "Repeat password",
validationState: confirmState
)
// Filled style
VStack(spacing: 12) {
DFTextField("Email", text: $email, placeholder: "you@example.com") {
Image(systemName: "envelope")
}
DFSecureField("Password", text: $password, placeholder: "Password")
DFButton("Sign in") { signIn() }
}
.dfTextFieldStyle(.filled)
.dfSecureFieldStyle(.filled)A themed wrapper around SwiftUI's native Toggle.
public init(_ label: String, isOn: Binding<Bool>)| Style | Token | Default | Description |
|---|---|---|---|
DFSwitchToggleStyle |
.switch |
Yes | Wraps native Toggle with .toggleStyle(.switch) and theme.colors.primary tint. |
DFCheckboxToggleStyle |
.checkbox |
No | Custom 20×20 pt rounded square checkbox with a bold checkmark. |
DFGlassToggleStyle |
.glass |
No |
.regularMaterial checkbox; requires iOS 26+ / macOS 26+. |
// Default switch
DFToggle("Enable notifications", isOn: $notificationsEnabled)
// Checkbox style
DFToggle("I agree to the terms of service", isOn: $agreed)
.dfToggleStyle(.checkbox)
// Apply one style to a group
VStack {
DFToggle("Marketing emails", isOn: $marketing)
DFToggle("Product updates", isOn: $updates)
DFToggle("Security alerts", isOn: $security)
}
.dfToggleStyle(.checkbox)
// Disabled
DFToggle("Feature flag (read-only)", isOn: .constant(true))
.disabled(true)A themed slider that wraps SwiftUI's Slider.
public init(
_ label: String? = nil,
value: Binding<Double>,
in range: ClosedRange<Double> = 0...1,
step: Double? = nil
)| Style | Token | Default | Description |
|---|---|---|---|
DFStandardSliderStyle |
.standard |
Yes | Label above track; no value or end-cap labels. |
DFLabeledSliderStyle |
.labeled |
No | Label + current value; min/max captions below track. |
DFGlassSliderStyle |
.glass |
No | Labeled layout inside .regularMaterial; requires iOS 26+. |
// Minimal
DFSlider(value: $opacity)
// With label
DFSlider("Volume", value: $volume, in: 0...100)
// Stepped
DFSlider("Quality", value: $quality, in: 1...5, step: 1)
// Labeled style
DFSlider("Font size", value: $fontSize, in: 10...36, step: 1)
.dfSliderStyle(.labeled)A themed picker that wraps SwiftUI's Picker. Works with any Hashable & Sendable selection type.
public init(
_ label: String,
selection: Binding<SelectionValue>,
@ViewBuilder content: () -> Content
) where SelectionValue: Hashable & Sendable, Content: View| Style | Token | Default | Description |
|---|---|---|---|
DFMenuPickerStyle |
.menu |
Yes | Dropdown menu on all platforms. Tinted with theme.colors.primary. |
DFSegmentedPickerStyle |
.segmented |
No | Horizontal segmented control. Native appearance. |
DFWheelPickerStyle |
.wheel |
No | Wheel on iOS/watchOS; falls back to .menu on macOS. |
DFGlassPickerStyle |
.glass |
No |
.menu style in .regularMaterial; requires iOS 26+. |
enum Role: String, CaseIterable, Hashable {
case viewer, editor, admin
var displayName: String { rawValue.capitalized }
}
@State private var role = Role.viewer
DFPicker("Role", selection: $role) {
ForEach(Role.allCases, id: \.self) {
Text($0.displayName).tag($0)
}
}
// Segmented style
DFPicker("Sort", selection: $sort) {
ForEach(SortOrder.allCases, id: \.self) {
Text($0.rawValue.capitalized).tag($0)
}
}
.dfPickerStyle(.segmented)A themed date picker that wraps SwiftUI's DatePicker.
public init(
_ label: String,
selection: Binding<Date>,
in dateRange: ClosedRange<Date>? = nil,
displayedComponents: DatePickerComponents = [.date]
)| Style | Token | Default |
|---|---|---|
DFCompactDatePickerStyle |
.compact |
Yes |
DFGraphicalDatePickerStyle |
.graphical |
No |
DFWheelDatePickerStyle |
.wheel |
No |
DFGlassDatePickerStyle |
.glass |
No — requires iOS 26+ |
// Date only, compact
DFDatePicker("Date of birth", selection: $birthdate)
// Date and time
DFDatePicker(
"Scheduled for",
selection: $scheduledDate,
displayedComponents: [.date, .hourAndMinute]
)
// With min/max range
DFDatePicker(
"Expiry date",
selection: $expiryDate,
in: Date()...oneYearFromNow
)
// Graphical calendar
DFDatePicker("Pick a date", selection: $meetingDate)
.dfDatePickerStyle(.graphical)A standalone checkbox component — distinct from DFToggle's .checkbox style.
public init(isChecked: Binding<Bool>, label: String = "")// Minimal — terms agreement
DFCheckbox(isChecked: $agreed, label: "I agree to the Terms of Service")
// Multi-select list
ForEach(interests, id: \.self) { interest in
DFCheckbox(
isChecked: Binding(
get: { selected.contains(interest) },
set: { isOn in
if isOn { selected.insert(interest) }
else { selected.remove(interest) }
}
),
label: interest
)
}
// Disabled
DFCheckbox(isChecked: $granted, label: "Access camera")
.disabled(isLocked)DFValidationState is shared by DFTextField and DFSecureField:
public enum DFValidationState: Sendable, Equatable {
case none // No validation feedback shown
case error(String) // Red border + inline error message
case valid // Green border; no additional message
}private var emailState: DFValidationState {
guard !email.isEmpty else { return .none }
let valid = email.contains("@") && email.contains(".")
return valid ? .valid : .error("Enter a valid email address")
}
DFTextField("Email", text: $email, validationState: emailState) {
Image(systemName: "envelope")
}import SwiftUI
import DesignFoundation
struct UserProfileForm: View {
@State private var displayName = ""
@State private var email = ""
@State private var password = ""
@State private var isPublic = true
@State private var emailNotifications = false
@State private var timezone = "UTC"
@State private var birthday = Date()
@State private var contentMaturity = 0.5
@State private var agreedToTerms = false
@State private var agreedToMarketing = false
private var emailState: DFValidationState {
guard !email.isEmpty else { return .none }
return email.contains("@") ? .valid : .error("Enter a valid email address")
}
private var passwordState: DFValidationState {
guard !password.isEmpty else { return .none }
return password.count >= 8 ? .valid : .error("Must be at least 8 characters")
}
private let timezones = ["UTC", "America/New_York", "America/Los_Angeles", "Europe/London"]
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 24) {
DFTextField("Display name", text: $displayName, placeholder: "Your public name") {
Image(systemName: "person")
}
DFTextField(
"Email",
text: $email,
placeholder: "you@example.com",
validationState: emailState
) {
Image(systemName: "envelope")
}
DFSecureField(
"Password",
text: $password,
placeholder: "At least 8 characters",
validationState: passwordState
)
DFPicker("Timezone", selection: $timezone) {
ForEach(timezones, id: \.self) { Text($0).tag($0) }
}
DFDatePicker("Birthday", selection: $birthday, in: ...Date())
.dfDatePickerStyle(.compact)
DFSlider("Content maturity", value: $contentMaturity, in: 0...1)
.dfSliderStyle(.labeled)
DFToggle("Public profile", isOn: $isPublic)
DFToggle("Email notifications", isOn: $emailNotifications)
DFCheckbox(isChecked: $agreedToTerms, label: "I agree to the Terms of Service")
DFCheckbox(isChecked: $agreedToMarketing, label: "Send me product updates")
DFButton("Save profile") { saveProfile() }
.disabled(!agreedToTerms)
.padding(.top, 8)
}
.padding()
}
.dfTextFieldStyle(.filled)
.dfSecureFieldStyle(.filled)
}
private func saveProfile() { /* persistence logic */ }
}See also: Buttons · Theming · Forms-and-Validation · Lists-Tables-and-Data · Getting-Started