Ora Browser provides a beautiful, native macOS appearance with support for light and dark themes. The appearance system is built using SwiftUI and integrates seamlessly with macOS system preferences.
Appearance Modes
Ora supports three appearance modes:
Light Bright, clean interface optimized for daytime use
Dark Dark theme that’s easy on the eyes in low-light conditions
Auto Follows macOS system appearance (recommended)
Changing Appearance
The appearance selector is located in General Settings (⌘,).
Appearance Selector UI
The appearance selector displays visual previews of each theme:
struct AppearanceSelector : View {
@Binding var selection: AppAppearance
@Environment (\. theme ) var theme
private var options: [Option] {
[
. init ( appearance : . light , imageName : "appearance-light" , title : "Light" ),
. init ( appearance : . dark , imageName : "appearance-dark" , title : "Dark" ),
. init ( appearance : . system , imageName : "appearance-system" , title : "Auto" )
]
}
var body: some View {
VStack ( alignment : . leading , spacing : 8 ) {
Text ( "Appearance" ). foregroundStyle (. secondary )
HStack ( spacing : 16 ) {
ForEach (options) { opt in
let isSelected = selection == opt. appearance
Button {
selection = opt. appearance
} label : {
VStack ( alignment : . leading , spacing : 8 ) {
Image (opt. imageName )
. resizable ()
. scaledToFit ()
. frame ( width : 105 , height : 68 )
. clipShape ( RoundedRectangle ( cornerRadius : 8 , style : . continuous ))
Text (opt. title )
. fontWeight (isSelected ? . semibold : . regular )
}
. padding ( 6 )
. background (
RoundedRectangle ( cornerRadius : 10 , style : . continuous )
. fill (isSelected ? theme. foreground . opacity ( 0.12 ) : Color. clear )
)
}
. buttonStyle (. plain )
}
}
}
}
}
The selected appearance option is visually highlighted with a subtle background and bold text.
Appearance Manager
Appearance is controlled by the AppearanceManager singleton:
enum AppAppearance : String , CaseIterable , Identifiable {
case system = "System"
case light = "Light"
case dark = "Dark"
var id: String {
rawValue
}
}
class AppearanceManager : ObservableObject {
static let shared = AppearanceManager ()
@AppStorage ( "ui.app.appearance" ) var appearance: AppAppearance = . system {
didSet {
updateAppearance ()
}
}
func updateAppearance () {
guard NSApp != nil else {
print ( "NSApp is nil, skipping appearance update" )
return
}
switch appearance {
case . system :
NSApp. appearance = nil
case . light :
NSApp. appearance = NSAppearance ( named : . aqua )
case . dark :
NSApp. appearance = NSAppearance ( named : . darkAqua )
}
}
}
How It Works
User selects appearance
Selection is bound to AppearanceManager.shared.appearance
Setting is persisted
@AppStorage automatically saves the preference to UserDefaults with key "ui.app.appearance"
Appearance updates
The didSet observer calls updateAppearance() which applies the macOS appearance
UI refreshes
SwiftUI automatically rebuilds the interface with the new theme
Theme System
Ora uses a comprehensive theme system that adapts colors based on the current color scheme.
Theme Structure
struct Theme : Equatable {
let colorScheme: ColorScheme
var primary: Color {
Color ( hex : "#f3e5d6" )
}
var primaryDark: Color {
Color ( hex : "#141414" )
}
var accent: Color {
Color ( hex : "#FF5F57" )
}
var background: Color {
colorScheme == . dark ? Color ( hex : "#0F0E0E" ) : . white
}
var foreground: Color {
colorScheme == . dark ? . white : . black
}
}
Core Colors
Brand Colors
Adaptive Colors
Window Colors
var primary: Color {
Color ( hex : "#f3e5d6" ) // Warm cream
}
var primaryDark: Color {
Color ( hex : "#141414" ) // Deep charcoal
}
var accent: Color {
Color ( hex : "#FF5F57" ) // Vibrant red
}
var background: Color {
colorScheme == . dark ? Color ( hex : "#0F0E0E" ) : . white
}
var foreground: Color {
colorScheme == . dark ? . white : . black
}
var mutedForeground: Color {
. secondary
}
var subtleWindowBackgroundColor: Color {
colorScheme == . dark
? self . primaryDark . opacity ( 0.3 )
: self . primary . opacity ( 0.3 )
}
var solidWindowBackgroundColor: Color {
colorScheme == . dark ? self . primaryDark : self . primary
}
var invertedSolidWindowBackgroundColor: Color {
colorScheme == . dark ? self . primary : self . primaryDark
}
UI Component Colors
var activeTabBackground: Color {
colorScheme == . dark
? . white . opacity ( 0.15 )
: self . primaryDark . opacity ( 0.8 )
}
var mutedBackground: Color {
colorScheme == . dark
? . white . opacity ( 0.17 )
: self . primaryDark . opacity ( 0.1 )
}
var popoverBackground: Color {
colorScheme == . dark ? Color ( hex : "#242424" ) : . white
}
var popoverMutedBackground: Color {
colorScheme == . dark
? Color ( hex : "#1d1d1d" )
: self . primaryDark . opacity ( 0.1 )
}
Status Colors
Ora includes semantic colors for status indicators:
var destructive: Color {
Color ( hex : "#FF6969" )
}
var success: Color {
Color ( hex : "#93DA97" )
}
var warning: Color {
Color ( hex : "#FFBF78" )
}
var info: Color {
Color ( hex : "#799EFF" )
}
Destructive #FF6969 - For delete, remove, or dangerous actions
Success #93DA97 - For successful operations and confirmations
Warning #FFBF78 - For warnings and cautions
Info #799EFF - For informational messages
Search Engine Brand Colors
The theme includes colors for popular search engines and services:
var grok: Color {
colorScheme == . dark ? . white : . black
}
var claude: Color {
Color ( hex : "#d97757" )
}
var openai: Color {
colorScheme == . dark ? . white : . black
}
var perplexity: Color {
Color ( hex : "#20808D" )
}
var google: Color {
. blue
}
var youtube: Color {
Color ( hex : "#FC0D1B" )
}
var github: Color {
colorScheme == . dark ? . white : Color ( hex : "#181717" )
}
Using the Theme System
Accessing Theme in Views
The theme is available via SwiftUI’s environment:
struct MyView : View {
@Environment (\. theme ) var theme
var body: some View {
Text ( "Hello, World!" )
. foregroundColor (theme. foreground )
. background (theme. background )
}
}
Theme Provider
The ThemeProvider modifier injects the theme into the environment:
struct ThemeProvider : ViewModifier {
@Environment (\. colorScheme ) private var colorScheme
func body ( content : Content) -> some View {
content. environment (\. theme , Theme ( colorScheme : colorScheme))
}
}
Apply it to your root view:
ContentView ()
. modifier ( ThemeProvider ())
Custom Theme Key
private struct ThemeKey : EnvironmentKey {
static let defaultValue = Theme ( colorScheme : . light )
}
extension EnvironmentValues {
var theme: Theme {
get { self [ThemeKey. self ] }
set { self [ThemeKey. self ] = newValue }
}
}
Appearance Best Practices
Use Auto Mode The Auto appearance mode provides the best experience by matching your system settings and adapting to time of day
Test Both Themes When customizing UI components, test in both light and dark modes to ensure proper contrast and readability
Use Theme Colors Always use theme.foreground, theme.background, etc. instead of hardcoded colors for consistent theming
Respect Accessibility The theme system uses macOS semantic colors (like .secondary) that respect accessibility settings
Custom Colors
When you need to use custom hex colors, use the Color(hex:) initializer:
Color ( hex : "#FF5F57" ) // Converts hex string to SwiftUI Color
Avoid using custom colors for text and backgrounds. Instead, use theme colors that automatically adapt to light and dark modes.
Appearance Persistence
The selected appearance is automatically saved using @AppStorage:
@AppStorage ( "ui.app.appearance" ) var appearance: AppAppearance = . system
This ensures your appearance preference persists across app launches.
Dynamic Color Adaptation
Many theme colors adapt based on the color scheme:
// Example: Tab backgrounds change based on theme
var activeTabBackground: Color {
colorScheme == . dark
? . white . opacity ( 0.15 ) // Light overlay in dark mode
: self . primaryDark . opacity ( 0.8 ) // Dark overlay in light mode
}
This pattern ensures optimal contrast in both light and dark modes.
Advanced Theming
Launcher Colors
var launcherMainBackground: Color {
colorScheme == . dark
? Color (. windowBackgroundColor ). opacity ( 0.7 )
: . white . opacity ( 0.8 )
}
The launcher (command palette) uses semi-transparent backgrounds that adapt to the theme.
Border and Separator Colors
var border: Color {
Color (. separatorColor )
}
var placeholder: Color {
Color (. placeholderTextColor )
}
These use macOS system colors that automatically adapt to the appearance and respect user accessibility settings.
Troubleshooting
If the appearance doesn’t change immediately:
Check that NSApp is not nil when updateAppearance() is called
Verify the appearance is being saved to UserDefaults
Restart the app to ensure a clean state
The appearance manager includes a safety check: guard NSApp != nil else {
print ( "NSApp is nil, skipping appearance update" )
return
}
Custom colors not adapting
If custom UI elements don’t adapt to theme changes:
Ensure you’re using @Environment(\.theme) to access the theme
Use theme colors instead of hardcoded colors
Apply ThemeProvider modifier to the root view
Check that the view is being rebuilt when the theme changes
Auto mode not following system
If Auto mode doesn’t track system appearance:
Verify System Preferences > General > Appearance is set correctly
Check that the app has permission to read system appearance
Ensure NSApp.appearance = nil is being set for system mode