Components
BottomSheet
A modal sheet that rises from the bottom over a Scrim — an optional visual, icon, title, description and pills, then a body and a footer.
Figma description ·
bottom-sheetOpen in Figma ↗This component has no description in Figma yet.
Props
import SwiftUI import EandTokens // V1.2 bottom-sheet: surface/sunken/inverse, top radius 24, over the Scrim. Grabber 40×4 // (midnight glass 20%). Header 20/20/0 (bottom 20): optional 40pt r12 icon, a 20pt / 110% // bold title, body/md description; close is a 40pt section-link button. Body padded // 0/20/20 on surface/base/default; the footer is padded 20. struct BottomSheet<Content: View>: View { let title: String var onClose: () -> Void = {} @ViewBuilder var content: Content var body: some View { VStack(spacing: 0) { Capsule().fill(EandColor.surfaceGlassMidnightMd) .frame(width: EandScale.scale40, height: EandScale.scale4) .padding(.top, EandSpacing.xs) HStack(alignment: .top, spacing: EandSpacing.md) { Text(title) .font(.system(size: EandTypography.headingMd.size, weight: EandTypography.headingMd.weight)) .padding(.vertical, EandSpacing.xs) Spacer(minLength: 0) CloseLink(action: onClose) // solid white, 40pt, r14 } .padding([.top, .horizontal], EandSpacing.xl).padding(.bottom, EandSpacing.xl) content .padding([.horizontal, .bottom], EandSpacing.xl) .frame(maxWidth: .infinity) .background(EandColor.surfaceBaseDefault) } .foregroundColor(EandColor.textDefaultDefault) .background(EandColor.surfaceSunkenInverse) .clipShape(UnevenRoundedRectangle(topLeadingRadius: EandRadius.radius8, topTrailingRadius: EandRadius.radius8)) } }
When to use
- Use for a focused task or detail that keeps the user in context: choosing an option, reviewing details before paying, reading a system message.
- Don't use for a whole flow (use a page), for the outcome of a flow (use
AlertModal with
integration="bottom-sheet"), or for a passing confirmation (use Snackbar).
Choosing a type
type | Figma composition |
|---|---|
basic | Visual, icon, title and description |
detailed (default) | Title and body |
system-message | Close button and body, no grabber |
type records the Figma variant and sets the grabber default: on, except for
system-message. The content you pass decides what renders.
Anatomy
- Grabber — a 40 × 4 pill at the top.
grabberoverrides the type's default. - Visual —
visual, media above the header, framed with a 4px inset and a 16px radius. - Title block — an
iconin a 40px tile, thetitlein bold 20px, and adescriptioninbody/md. - Pills —
pills, a row of Chips 16px under the title that scrolls sideways. - Body —
children, in a scrolling content area onsurface/base/default. - Footer —
footer, for example block buttons or a CTA Bar.
Setting onDismiss shows a close button and lets a scrim click dismiss the sheet. The close
sits at the end of the title row, or over the visual when there is one. closeButton={false}
hides it.
<BottomSheet
title="Choose a plan"
description="Prices include VAT."
pills={<><Chip selected>Monthly</Chip><Chip>Yearly</Chip></>}
onDismiss={close}
footer={<Button block onClick={confirm}>Continue</Button>}
>
…
</BottomSheet>Deprecated
display="dark" has no V1.2 counterpart; the sheet is always light. subheader is replaced by
pills; it still renders under the title.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
closeButton? | boolean | — | Figma close-button. Default: shown whenever onDismiss is set. |
description? | ReactNode | — | Figma text-secondary — body/md under the title. |
display? | 'light' | 'dark' | light | Deprecated. V1.2 has no Display axis; the sheet is always light. |
footer? | ReactNode | — | |
grabber? | boolean | — | Figma grabber boolean. Default: on, except for system-message. |
icon? | ReactNode | — | Figma icon — a 40px logo tile above the title (pass an image or an icon). |
onDismiss? | (() => void) | — | |
open? | boolean | true | |
pills? | ReactNode | — | Figma pills — a row of Chips under the title. |
subheader? | ReactNode | — | Deprecated. V1.1 Subheader. Use pills; still rendered under the title. |
title? | ReactNode | — | |
type? | 'basic' | 'detailed' | 'system-message' | detailed | Figma type. system-message drops the grabber by default. Default detailed. |
visual? | ReactNode | — | Figma visual-asset — media above the header, framed with a 4px inset and r16. |
Design
Figma source: bottom-sheet — etisalat Consumer App DSL V1.2
- Sheet:
surface/sunken/inverse(#f0f0f5) with 24px top corners (border-radius/8), on a Scrim:surface/overlay/scrimunder a 20px blur. - Title frame padding 20/20/0 (
spacing/xl); the title bindsfont/size/6(20px) at 110%; the header ends with 20px of padding. - Close: a 40px section-link button, solid in the title row and glass inverse 20px in from the top end over a visual.
- Body padding 0/20/20 on
surface/base/default; footer padding 20. - Grabber: 40 × 4 on
surface/glass/midnight/md, in an 8px frame.