@@ -17,19 +17,26 @@ public struct BottomSheet<Content: View>: View {
1717 /// when natural sizing is needed — it guarantees `fillsHeight: false` and the modifier are
1818 /// always applied together.
1919 public var fillsHeight : Bool = true
20+ /// Fill behind the whole sheet — grabber, header and content alike. Also
21+ /// used as the presentation background so the home-indicator inset the
22+ /// detent adds matches; a host that only restyles its own content would
23+ /// otherwise get a strip of this colour along the bottom edge.
24+ public var background : Color = . dash. primaryBackground
2025 @ViewBuilder public var content : ( ) -> Content
2126
2227 public init (
2328 title: String = " " ,
2429 showBackButton: Binding < Bool > ,
2530 onBackButtonPressed: ( ( ) -> Void ) ? = nil ,
2631 fillsHeight: Bool = true ,
32+ background: Color = . dash. primaryBackground,
2733 @ViewBuilder content: @escaping ( ) -> Content
2834 ) {
2935 self . title = title
3036 self . _showBackButton = showBackButton
3137 self . onBackButtonPressed = onBackButtonPressed
3238 self . fillsHeight = fillsHeight
39+ self . background = background
3340 self . content = content
3441 }
3542
@@ -42,7 +49,7 @@ public struct BottomSheet<Content: View>: View {
4249
4350 contentSection
4451 }
45- . background ( Color . dash . primaryBackground )
52+ . background ( background )
4653
4754 if fillsHeight {
4855 sheet. edgesIgnoringSafeArea ( . bottom)
@@ -103,13 +110,13 @@ public struct BottomSheet<Content: View>: View {
103110 . navigationBarHidden( true )
104111 #endif
105112 . frame( maxWidth: . infinity, maxHeight: . infinity)
106- . background ( Color . dash . primaryBackground )
113+ . background ( background )
107114 }
108115 } else {
109116 // Natural height — no greedy NavigationView / maxHeight so the sheet can self-size.
110117 content ( )
111118 . frame ( maxWidth: . infinity)
112- . background ( Color . dash . primaryBackground )
119+ . background ( background )
113120 }
114121 }
115122}
@@ -134,6 +141,7 @@ public extension BottomSheet {
134141 onBackButtonPressed: ( ( ) -> Void ) ? = nil ,
135142 fallback: CGFloat = 0 ,
136143 maxHeightFraction: CGFloat = 0.95 ,
144+ background: Color = . dash. primaryBackground,
137145 cornerRadius: CGFloat ? = nil ,
138146 @ViewBuilder content: @escaping ( ) -> Content
139147 ) -> some View {
@@ -142,9 +150,14 @@ public extension BottomSheet {
142150 showBackButton: showBackButton,
143151 onBackButtonPressed: onBackButtonPressed,
144152 fillsHeight: false ,
153+ background: background,
145154 content: content
146155 )
147- . selfSizingSheet ( fallback: fallback, maxHeightFraction: maxHeightFraction, cornerRadius: cornerRadius)
156+ . selfSizingSheet (
157+ fallback: fallback,
158+ maxHeightFraction: maxHeightFraction,
159+ background: background,
160+ cornerRadius: cornerRadius)
148161 }
149162}
150163
@@ -163,32 +176,50 @@ public extension View {
163176 /// - fallback: Height used before the first measurement (avoids a `.medium` flash).
164177 /// - maxHeightFraction: Caps the sheet at this fraction of the window height; taller content
165178 /// is clipped, so wrap it in a `ScrollView`.
179+ /// - background: Fill for the sheet and its presentation, so the bottom
180+ /// safe-area strip matches the content. This modifier cannot see the
181+ /// colour the wrapped `BottomSheet` was built with, so a custom one has
182+ /// to be passed here too — or use `BottomSheet.selfSizing(...)`, which
183+ /// forwards a single `background` to both.
166184 /// - cornerRadius: Optional corner radius applied via `presentationCornerRadius` on
167- /// iOS 16.4..<26 (iOS 26+ keeps the system corner styling). When provided, the sheet
168- /// background is also filled so the bottom safe-area strip matches the content.
185+ /// iOS 16.4..<26 (iOS 26+ keeps the system corner styling).
169186 @ViewBuilder
170187 func selfSizingSheet(
171188 fallback: CGFloat = 0 ,
172189 maxHeightFraction: CGFloat = 0.95 ,
190+ background: Color = . dash. primaryBackground,
173191 cornerRadius: CGFloat ? = nil
174192 ) -> some View {
175193 if #available( iOS 16 . 0 , macOS 13 . 0 , * ) {
176194 let modified = modifier ( SelfSizingSheetModifier ( fallback: fallback, maxHeightFraction: maxHeightFraction) )
177195 #if os(iOS)
178- if #available( iOS 16 . 4 , * ) , let cornerRadius {
179- if #unavailable( iOS 26 . 0 ) {
180- // iOS 16.4..<26: apply the custom corner radius + fill the sheet background.
196+ if #available( iOS 16 . 4 , * ) {
197+ // The background is filled whatever the corner radius: the
198+ // measured height excludes the home-indicator inset that
199+ // `.presentationDetents([.height])` adds back, so that strip
200+ // sits outside the sheet's own `VStack` and shows the system
201+ // background unless this fills it.
202+ if #unavailable( iOS 26 . 0 ) , let cornerRadius {
181203 modified
182204 . presentationCornerRadius ( cornerRadius)
183- . presentationBackground ( Color . dash . primaryBackground )
205+ . presentationBackground ( background )
184206 } else {
185- // iOS 26+: keep the system corner styling, just fill the background .
207+ // iOS 26+ keeps the system corner styling.
186208 modified
187- . presentationBackground ( Color . dash . primaryBackground )
209+ . presentationBackground ( background )
188210 }
189211 } else {
190212 modified
191213 }
214+ #elseif os(macOS)
215+ // `presentationCornerRadius` is iOS-only, but the presentation
216+ // background lands on macOS 13.3 — apply it there too so the
217+ // parameter is not silently ignored.
218+ if #available( macOS 13 . 3 , * ) {
219+ modified. presentationBackground ( background)
220+ } else {
221+ modified
222+ }
192223 #else
193224 modified
194225 #endif
@@ -245,6 +276,8 @@ private struct SelfSizingSheetModifier: ViewModifier {
245276 }
246277}
247278
279+ #if DEBUG
280+
248281@available ( iOS 17 , macOS 14 , * )
249282#Preview( " BottomSheet Filled Height " ) {
250283 BottomSheet (
@@ -283,3 +316,27 @@ private struct SelfSizingSheetModifier: ViewModifier {
283316 . padding ( )
284317 }
285318}
319+
320+ @available ( iOS 17 , macOS 14 , * )
321+ #Preview( " BottomSheet Custom Background " ) {
322+ BottomSheet (
323+ title: " Bottom Sheet " ,
324+ showBackButton: . constant( false ) ,
325+ fillsHeight: false ,
326+ background: . dash. secondaryBackground
327+ ) {
328+ VStack ( alignment: . leading, spacing: 12 ) {
329+ Text ( " Cards on a tinted sheet " )
330+ . dashFont ( . calloutMedium)
331+ . foregroundColor ( . dash. primaryText)
332+
333+ Text ( " The host picks the fill; cards drawn on top keep their own. " )
334+ . dashFont ( . body)
335+ . foregroundColor ( . dash. secondaryText)
336+ . modifier ( MenuViewModifier ( ) )
337+ }
338+ . padding ( )
339+ }
340+ }
341+
342+ #endif
0 commit comments