Themes

Themes define the global visual styling of your site. They control colors, typography, layout constraints, and syntax highlighting, applied consistently across all pages and posts.

Themes are declarative, composable, and evaluated at render time, letting you adapt styling based on the active color scheme or user selection.

Defining a Theme

Create a theme by conforming to the Theme protocol and implementing theme(site:colorScheme:):

struct MyTheme: Theme {
   func theme(site: Content, colorScheme: ColorScheme) -> Content {
       if colorScheme == .dark {
           site.accent(.indigo)
       } else if colorScheme == .light {
           site.accent(.blue)
       } else {
           site
               .syntaxHighlighterTheme(.xcode)
               .codeFont(.gintronic)
               .contentWidth(max: 800)
       }
   }
}

The method receives a proxy of the current site and the resolved color scheme. Return a modified version with your desired styling applied.


Understanding Color Schemes

Color schemes aren’t binary—they have three possible values:

  • .light — Light appearance
  • .dark — Dark appearance
  • .any — Shared styles that apply regardless of appearance

This matters when writing conditional logic:

struct MyTheme: Theme {
   func theme(site: Content, colorScheme: ColorScheme) -> Content {
       if colorScheme == .dark {
           site.font(.mdIO)
       } else {
           site
               .contentWidth(max: 800)
               .accent(.red)
       }
   }
}

The else block applies to both .light and .any, not just “the opposite of dark”. If you want distinct styling for light and dark, check both explicitly.


Theme Modifiers

Configure themes using fluent modifiers for colors, typography, and layout:

site
    .accent(.blue)
    .background(.gray)
    .font(.system)
    .titleFont(.serif)
    .codeFont(.monospaced)
    .contentWidth(max: 800)
    .syntaxHighlighterTheme(.github)

Common categories include:

  • Colors
    accent(), foregroundStyle(), background()

  • Typography
    font(), titleFont(), codeFont(), fontSize(), fontWeight(), lineSpacing()

  • Layout
    contentWidth(), inlineCodeStyle()

  • Code Highlighting
    syntaxHighlighterTheme() (see Syntax Highlighting)


Registering Themes

Register themes directly on your site:

struct MySite: Site {
   var themes: [any Theme] {
       MyTheme()
       AnotherTheme()
   }
}

When multiple themes are present, Raptor treats the first as the default.


Color Schemes and Themes

Themes and color schemes are independent concepts:

  • A theme defines what styles exist
  • A color scheme defines which appearance is active

This lets users switch themes without affecting their light/dark preference—and vice versa.

Switching Themes

Let users pick a theme:

struct ThemePicker: HTML {
   @Environment(\.themes) private var themes

   var body: some HTML {
       SegmentedControl {
           InlineForEach(themes) { theme in
               Button(
                   theme.name.capitalized,
                   action: .switchTheme(theme)
               )
           }
       }
       .selectionPersisted()
   }
}

Switching Color Schemes

Let users toggle light/dark mode:

SegmentedControl {
   InlineForEach(ColorScheme.allCases) { scheme in
       Button(
           scheme.name,
           action: .switchColorScheme(scheme)
       )
   }
}
.selectionPersisted()

These controls can coexist, giving users full control over both appearance axes.


Forcing Color Schemes

By default, sites follow the user or system preference (.automatic), but you can force a specific appearance site-wide:

struct MySite: Site {
   var colorScheme: ColorScheme = .light 
}

You can also force a color scheme for individual elements using preferredColorScheme(_:):

Text("Dark Background")
   .padding()
   .background(.thickMaterial)
   .preferredColorScheme(.dark)

This is useful for elements like dark-themed navigation bars or light hero sections that should maintain their appearance regardless of the user’s preference.