Text Field Group

macOS 13.3+

Wraps a text field in a shared box with optional leading and trailing addons.

import SwiftUIstruct TextFieldGroupUsageExample: View {    @State private var search = ""    var body: some View {        FLXTextFieldGroup("Search", text: $search) {            FLXTextFieldGroupAddon { Image(systemName: "magnifyingglass") }        }        .padding(FLXSpacing.xl)    }}

Installation

Requires the design system foundation (tokens and theme), plus FLXTextField and FLXSecureField.

pnpm dlx shadcn@latest add https://swiftui.flexnative.com/r/mac-text-field-group.json

Usage

FLXTextFieldGroup("Search", text: $search) {    FLXTextFieldGroupAddon {        Image(systemName: "magnifyingglass")    }}

Composition

FLXTextFieldGroup draws a single shared box around an optional leading addon, the field, and an optional trailing addon. FLXTextFieldGroupAddon tints its content with the theme's muted foreground, for an icon, a Text prefix/suffix, or a Button.

On macOS, the border takes the theme's ring color while the field has focus, since the Mac doesn't draw its own focus ring around it.

FLXTextFieldGroup("0.00", text: $amount, leading: {  FLXTextFieldGroupAddon { Text("$") }}, trailing: {  FLXTextFieldGroupAddon { Text("USD") }})

There are two initializers: leading-only, and leading-and-trailing. With a single trailing closure, FLXTextFieldGroup(...) { ... } always binds to leading.

Secure variant

FLXSecureTextFieldGroup draws the same box, wrapping FLXSecureField instead of FLXTextField.

FLXSecureTextFieldGroup("Password", text: $password) {  FLXTextFieldGroupAddon { Image(systemName: "lock") }}

Custom styling

FLXTextFieldGroupStyle is an FLX protocol. Override it for a subtree via .flxTextFieldGroupStyle(_:):

struct FLXPillTextFieldGroupStyle: FLXTextFieldGroupStyle {  func makeBody(configuration: Configuration) -> some View {      configuration.content          .padding(.horizontal, FLXSpacing.xs)          .padding(.vertical, FLXSpacing.xs)          .background(.quaternary, in: Capsule())  }}VStack(spacing: FLXSpacing.md) {  FLXTextFieldGroup("Search", text: $search) {      FLXTextFieldGroupAddon { Image(systemName: "magnifyingglass") }  }  FLXTextFieldGroup("Search", text: $search) {      FLXTextFieldGroupAddon { Image(systemName: "magnifyingglass") }  }  .flxTextFieldGroupStyle(FLXPillTextFieldGroupStyle())}

configuration.content is the row with the field and addons, and configuration.isFocused tells whether the field has focus.

API Reference

FLXTextFieldGroup

init(    _ titleKey: LocalizedStringResource = "",    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() }) where Trailing == EmptyViewinit<S: StringProtocol>(    _ title: S,    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() }) where Trailing == EmptyViewinit(    _ titleKey: LocalizedStringResource = "",    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() },    @ViewBuilder trailing: @escaping () -> Trailing)init<S: StringProtocol>(    _ title: S,    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() },    @ViewBuilder trailing: @escaping () -> Trailing)

FLXTextFieldGroupAddon

init(@ViewBuilder content: @escaping () -> Content)

FLXSecureTextFieldGroup

init(    _ titleKey: LocalizedStringResource = "",    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() }) where Trailing == EmptyViewinit<S: StringProtocol>(    _ title: S,    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() }) where Trailing == EmptyViewinit(    _ titleKey: LocalizedStringResource = "",    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() },    @ViewBuilder trailing: @escaping () -> Trailing)init<S: StringProtocol>(    _ title: S,    text: Binding<String>,    @ViewBuilder leading: @escaping () -> Leading = { EmptyView() },    @ViewBuilder trailing: @escaping () -> Trailing)