Elegant, modular UI building blocks for SwiftUI
Inspired by floating ice sheets, FloeKit provides calm, elegant, and modular UI components designed for composability, design clarity, and reuse. It positions itself as a thoughtful layer on top of SwiftUI, offering a consistent design system with built-in theming, spacing, and typography.
π Table of Contents
- π¨ Consistent Design System - Unified colors, typography, spacing, and shadows
- π± Cross-Platform - Works seamlessly on iOS and macOS
- π Perfect Dark Mode - β Complete - All components tested and beautiful in both light and dark modes
- βΏ Accessibility First - Built-in VoiceOver, Dynamic Type, and comprehensive accessibility support
- ποΈ Advanced Architecture - Result builders, preference keys, haptic feedback, and sophisticated animations
- π§ Highly Customizable - Override any aspect while maintaining design consistency
- β‘ Performance Optimized - Efficient SwiftUI implementation with smooth 60fps animations
- π¦ Zero Dependencies - Pure SwiftUI implementation with no external dependencies
- π― Production Ready - Battle-tested components with proper error handling and edge cases
Add FloeKit to your project through Xcode:
- File β Add Package Dependencies
- Enter package URL:
https://github.com/nemanjavlahovic/FloeKit
- Select version and add to target
Or add to your Package.swift
:
dependencies: [
.package(url: "https://github.com/nemanjavlahovic/FloeKit", from: "0.3.0")
]
Soft, elevated buttons with multiple sizes, loading states, and icon support.
import FloeKit
// Basic button
FloeButton("Get Started") {
// Action
}
// Button with icon and custom styling
// Note: Parameters can be specified in any order using named parameters
FloeButton("Save",
size: .large,
backgroundColor: .blue,
textColor: .white,
icon: Image(systemName: "checkmark")) {
// Save action
}
// Loading state
FloeButton("Processing...", isLoading: true) {
// Action
}
Sizes: .small
, .medium
, .large
Features: Loading states, icons, custom colors, accessibility support
Elegant text input with focus states, icons, validation, and character limits.
@State private var email = ""
@State private var password = ""
@State private var bio = ""
// Basic text field
FloeTextField(text: $email, placeholder: "Email address")
// With icons and validation
FloeTextField(
text: $email,
placeholder: "Enter email",
leadingIcon: Image(systemName: "envelope"),
errorMessage: "Invalid email format"
)
// Secure field
FloeTextField(
text: $password,
placeholder: "Password",
leadingIcon: Image(systemName: "lock"),
isSecure: true
)
// With character limit
FloeTextField(
text: $bio,
placeholder: "Bio",
characterLimit: 150
)
Features: Icons, secure input, validation, character limits, focus states
Clean, elevated containers with consistent shadows and padding.
FloeCard {
VStack {
Text("Card Title")
.floeFont(.headline)
Text("Card content goes here")
.floeFont(.body)
}
}
// Custom styling
FloeCard(backgroundColor: .blue.opacity(0.1),
shadowStyle: .elevated,
padding: .spacious) {
// Content
}
Features: Customizable shadows, padding presets, border support
Elegant, customizable avatar components with status indicators and grouping support.
// Basic avatars
FloeAvatar.initials("JD")
FloeAvatar.icon("person.fill")
FloeAvatar.placeholder()
// With image
FloeAvatar(image: Image("user-photo"))
// Different sizes
FloeAvatar.initials("SM", size: .small)
FloeAvatar.initials("LG", size: .large)
FloeAvatar.initials("XL", size: .extraLarge)
// With status indicators
FloeAvatar.online(initials: "ON")
FloeAvatar(initials: "AW", statusIndicator: .away)
FloeAvatar(initials: "BY", statusIndicator: .busy)
FloeAvatar(initials: "CT", statusIndicator: .custom(.purple))
// Custom styling
FloeAvatar(
initials: "VIP",
backgroundColor: .black,
foregroundColor: .yellow,
borderColor: .yellow,
borderWidth: 2,
shadowStyle: .elevated
)
// Interactive avatars
FloeAvatar.initials("TAP") {
print("Avatar tapped!")
}
Grouped Avatars:
// Stacked avatars with overlap
FloeAvatarGroup(
avatars: [avatar1, avatar2, avatar3],
style: .stacked,
maxVisible: 3
)
// Grid layout
FloeAvatarGroup(
avatars: avatarArray,
style: .grid(columns: 2)
)
Sizes: .small
, .medium
, .large
, .extraLarge
Status: Online, offline, away, busy, custom colors
Features: Images, initials, SF Symbols, borders, shadows, tap actions, grouping
Lightweight, animated toast notifications with swipe-to-dismiss and customizable styles.
@StateObject private var toastManager = FloeToastManager()
// Basic usage with convenience methods
.floeToast(FloeToast.success("Success!", message: "Operation completed"))
.floeToast(FloeToast.error("Error!", message: "Something went wrong"))
.floeToast(FloeToast.warning("Warning!", message: "Please check your input"))
.floeToast(FloeToast.info("Info", message: "New update available"))
// Custom toast with action
FloeToast("Custom Toast",
message: "With action button",
style: .custom(backgroundColor: .purple,
foregroundColor: .white,
icon: Image(systemName: "star.fill")),
actionTitle: "Action") {
// Action handler
}
// Using Toast Manager
toastManager.show(FloeToast.success("Saved successfully!"))
// Advanced configuration
FloeToast("Upload Complete",
message: "Your file has been uploaded",
style: .success,
position: .bottom,
duration: 5.0,
actionTitle: "View") {
// View file action
} onDismiss: {
print("Toast dismissed")
}
Styles: Success, warning, error, info, custom
Positions: Top, bottom
Features: Auto-dismiss, swipe gestures, action buttons, custom styling, animations
Modern floating tab bar with smooth animations and flexible configuration.
// Define tabs
let tabs = [
FloeTabBar.Tab.systemIcon(id: "home", title: "Home",
systemName: "house", selectedSystemName: "house.fill"),
FloeTabBar.Tab.systemIcon(id: "search", title: "Search",
systemName: "magnifyingglass", badge: "3"),
FloeTabBar.Tab.systemIcon(id: "favorites", title: "Favorites",
systemName: "heart", selectedSystemName: "heart.fill"),
FloeTabBar.Tab.systemIcon(id: "profile", title: "Profile",
systemName: "person", selectedSystemName: "person.fill")
]
// Basic tab bar
@State private var selectedTab = "home"
FloeTabBar(
tabs: tabs,
selectedTabId: selectedTab,
onTabSelected: { selectedTab = $0 }
)
// Floating style with central action
FloeTabBar(
tabs: tabs,
selectedTabId: selectedTab,
onTabSelected: { selectedTab = $0 },
style: .floating,
indicatorStyle: .pill,
centralAction: { print("Add new item") },
centralActionIcon: Image(systemName: "plus.circle.fill")
)
// Scrollable for many tabs
FloeTabBar(
tabs: manyTabs,
selectedTabId: selectedTab,
onTabSelected: { selectedTab = $0 },
isScrollable: true
)
// Complete tab bar controller
FloeTabBarController(
tabs: tabs,
initialSelection: "home",
style: .floating
) { selectedTab in
// Content for each tab
switch selectedTab {
case "home": HomeView()
case "search": SearchView()
case "favorites": FavoritesView()
case "profile": ProfileView()
default: EmptyView()
}
}
Styles: Floating, attached, minimal
Indicators: Pill, underline, background, none
Features: Badges, central action button, scrollable tabs, animations, custom icons
Result Builder Syntax:
// Declarative tab creation with @TabBuilder
FloeTabBar(
selectedTabId: selectedTab,
onTabSelected: { selectedTab = $0 },
style: .floating,
indicatorStyle: .pill
) {
FloeTabBar.Tab.systemIcon(id: "home", title: "Home", systemName: "house")
FloeTabBar.Tab.systemIcon(id: "search", title: "Search", systemName: "magnifyingglass")
FloeTabBar.Tab.systemIcon(id: "favorites", title: "Favorites", systemName: "heart")
FloeTabBar.Tab.systemIcon(id: "profile", title: "Profile", systemName: "person")
}
Customizable slider with haptic feedback, value labels, and both horizontal/vertical orientations.
@State private var volume: Double = 50
@State private var brightness: Double = 0.7
// Basic slider
FloeSlider(value: $volume, in: 0...100, showLabels: .value)
// Percentage slider with convenience method
FloeSlider.percentage(value: $volume, showLabels: true)
// Volume-style slider (0-1)
FloeSlider.volume(value: $brightness)
// Custom range with steps
FloeSlider(
value: $rating,
in: 0...5,
step: 0.5,
showLabels: .value,
showMinMax: true
)
// Vertical slider
FloeSlider(
value: $volume,
in: 0...100,
orientation: .vertical,
showLabels: .percentage
)
.frame(height: 200)
// Custom styling
FloeSlider(
value: $temperature,
in: 16...30,
showLabels: .custom({ "\(Int($0))Β°C" }),
fillColor: .orange,
thumbColor: .red,
enableHaptics: true
)
Orientations: Horizontal, vertical
Label Styles: None, value, percentage, custom formatter
Features: Haptic feedback, range indicators, step values, custom styling, accessibility
Rich text display and editing component with expansion controls and character limits.
@State private var editableText = ""
@State private var limitedText = ""
// Basic editable text view
FloeTextView(
text: $editableText,
placeholder: "Enter your thoughts...",
size: .medium
)
// Read-only with expansion
FloeTextView.readOnly(
text: "Long text content that can be expanded...",
expansionStyle: .readMore(previewLines: 2)
)
// With character limit
FloeTextView.withCharacterLimit(
text: $limitedText,
placeholder: "Bio (max 100 characters)",
characterLimit: 100,
size: .small
)
// Attributed text with rich formatting
FloeTextView.attributedText(
attributedString,
size: .medium,
expansionStyle: .readMore(previewLines: 3)
)
Sizes: .small
, .medium
, .large
Expansion Styles: Read more/less, character limits, custom
Features: Rich text support, smooth animations, character counting, accessibility
Versatile progress indicators with linear and circular styles, supporting both determinate and indeterminate states.
@State private var progress: Double = 0.65
// Linear progress indicators
FloeProgressIndicator(
progress: progress,
style: .linear,
size: .medium,
showPercentage: true
)
// Circular progress indicators
FloeProgressIndicator(
progress: 0.75,
style: .circular,
size: .large,
showPercentage: true
)
// Indeterminate loading
FloeProgressIndicator.indeterminate(
style: .circular,
size: .medium,
color: .blue
)
// State-based indicators
FloeProgressIndicator.loading(style: .circular, size: .medium)
FloeProgressIndicator.success(style: .circular, size: .medium)
FloeProgressIndicator.error(style: .circular, size: .medium)
// Custom styling
FloeProgressIndicator(
progress: progress,
style: .linear,
size: .large,
color: .purple,
backgroundColor: .gray.opacity(0.2),
showPercentage: true
)
Styles: Linear, circular
States: Determinate, indeterminate, loading, success, error
Sizes: .small
, .medium
, .large
Features: Smooth animations, custom colors, percentage display, accessibility
Here's how FloeKit components work together in a real application:
import SwiftUI
import FloeKit
struct ProfileView: View {
@State private var name = ""
@State private var bio = ""
@State private var selectedTab = "profile"
@State private var showSuccessToast = false
var body: some View {
FloeTabBarController(
initialSelection: "profile",
style: .floating
) {
FloeTabBar.Tab.systemIcon(id: "profile", title: "Profile", systemName: "person")
FloeTabBar.Tab.systemIcon(id: "settings", title: "Settings", systemName: "gear")
} content: { selectedTab in
ScrollView {
VStack(spacing: FloeSpacing.Size.lg.value) {
// Avatar Section
FloeCard {
VStack(spacing: FloeSpacing.Size.md.value) {
FloeAvatar.initials("JD", size: .extraLarge)
Text("John Doe")
.floeFont(.headline)
}
.frame(maxWidth: .infinity)
}
// Form Section
FloeCard(padding: .comfortable) {
VStack(spacing: FloeSpacing.Size.md.value) {
FloeTextField(
text: $name,
placeholder: "Full Name",
leadingIcon: Image(systemName: "person.fill")
)
FloeTextView.withCharacterLimit(
text: $bio,
placeholder: "Tell us about yourself...",
characterLimit: 150,
size: .medium
)
FloeButton("Save Profile",
size: .large,
backgroundColor: FloeColors.primary,
textColor: .white) {
showSuccessToast = true
}
}
}
// Progress Section
FloeCard {
VStack(alignment: .leading, spacing: FloeSpacing.Size.sm.value) {
Text("Profile Completion")
.floeFont(.headline)
FloeProgressIndicator(
progress: 0.75,
style: .linear,
size: .medium,
showPercentage: true
)
}
}
}
.floePadding(.comfortable)
}
.floeToast(showSuccessToast ?
FloeToast.success("Profile Updated!",
message: "Your changes have been saved") {
showSuccessToast = false
} : nil
)
}
}
}
This example demonstrates:
- FloeTabBarController with multiple screens
- FloeCard for clean content organization
- FloeAvatar for user representation
- FloeTextField with icons for form input
- FloeTextView with character limits for longer text
- FloeButton for primary actions
- FloeProgressIndicator for status display
- FloeToast for user feedback
- FloeSpacing and FloeFont for consistent styling
Adaptive color palette with seamless light/dark mode support.
// Primary semantic colors
.foregroundColor(FloeColors.primary) // Main brand color
.backgroundColor(FloeColors.secondary) // Secondary brand color
.foregroundColor(FloeColors.accent) // Accent highlights
.foregroundColor(FloeColors.error) // Error states
// Surface colors
.backgroundColor(FloeColors.background) // Main background
.backgroundColor(FloeColors.surface) // Card/component backgrounds
// Neutral scale for subtle elements
.foregroundColor(FloeColors.neutral0) // Pure contrast
.foregroundColor(FloeColors.neutral10) // High contrast text
.foregroundColor(FloeColors.neutral20) // Medium contrast borders
.foregroundColor(FloeColors.neutral30) // Low contrast dividers
.foregroundColor(FloeColors.neutral40) // Subtle text
.foregroundColor(FloeColors.neutral90) // Very subtle backgrounds
// State colors
.foregroundColor(FloeColors.success) // Success feedback
.foregroundColor(FloeColors.warning) // Warning states
β
All colors automatically adapt to light/dark mode
β
Consistent contrast ratios for accessibility
β
Can be overridden via color assets for custom theming
Typography system with semantic font styles.
Text("Headline")
.floeFont(.headline)
Text("Body text")
.floeFont(.body)
// Or with custom size and weight
Text("Custom")
.floeFont(size: .xl, weight: .bold)
// Available styles: .body, .caption, .button, .title, .headline, .subheadline
// Available sizes: .xs, .sm, .base, .lg, .xl, .xl2, .xl3, .xl4
Consistent spacing and padding system.
// Use spacing tokens
VStack(spacing: FloeSpacing.Size.lg.value) {
// Content
}
// Apply semantic padding
SomeView()
.floePadding(.card) // Standard card padding
.floePadding(.section) // Section container padding
.floePadding(.comfortable) // Comfortable all-around padding
.floePadding(.spacious) // Spacious padding
.floePadding(.generous) // Generous padding
// Custom spacing
SomeView()
.floePadding(.vertical, .lg)
.floePadding(.horizontal, .xl)
Consistent shadow system with automatic dark mode adaptation.
// Apply semantic shadows
RoundedRectangle(cornerRadius: 12)
.floeShadow(.soft) // Subtle shadow
.floeShadow(.medium) // Standard shadow
.floeShadow(.elevated) // Strong shadow
// Available styles: .none, .subtle, .soft, .medium, .elevated
Custom stepper component with enhanced visual feedback.
- Smooth animations and haptic feedback
- Custom styling and button designs
- Long press for rapid changes
- Custom step values and ranges
Modern date and time selection components.
- Inline and compact picker styles
- Custom styling with FloeKit design language
- Range selection support
- Localization and timezone support
Enhanced search bar with modern styling and functionality.
- Animated search icon and clear button
- Search suggestions and recent searches
- Voice input support
- Custom filtering and debouncing
Enhanced list and grid components with built-in styling.
- Pull-to-refresh and infinite scrolling
- Swipe actions and reordering
- Section headers with sticky behavior
- Loading states and empty state views
Modern color selection component.
- Multiple picker styles (wheel, palette, sliders)
- Custom color palettes and recent colors
- Hex, RGB, HSL input support
- Eyedropper functionality
Simple charting components for basic data visualization.
- Line, bar, and pie chart support
- Animated data updates
- Interactive tooltips and legends
- Customizable colors and styling
FloeKit supports comprehensive theming through color asset overrides:
- Add a
Colors.xcassets
to your app - Create color sets with FloeKit's color names:
FloePrimary
,FloeSecondary
,FloeAccent
FloeBackground
,FloeSurface
FloeNeutral0
,FloeNeutral10
,FloeNeutral20
,FloeNeutral30
,FloeNeutral40
,FloeNeutral90
- FloeKit will automatically use your custom colors
FloeKit is available under the MIT license. See LICENSE for details.
Inspired by modern design systems and the SwiftUI community's best practices.