Design System
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.

9:41
My account
050 123 4567
My plan
Freedom Live Plan 200
20GBleftLocal Data
200minleftMinutes

Choose a plan

All plans include 5G and free Smiles membership.

Freedom Live 20020 GB · 200 minAED 200.00per month
Freedom Live 40050 GB · 500 minAED 400.00per month
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

typeFigma composition
basicVisual, icon, title and description
detailed (default)Title and body
system-messageClose 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. grabber overrides the type's default.
  • Visual — visual, media above the header, framed with a 4px inset and a 16px radius.
  • Title block — an icon in a 40px tile, the title in bold 20px, and a description in body/md.
  • Pills — pills, a row of Chips 16px under the title that scrolls sideways.
  • Body — children, in a scrolling content area on surface/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

PropTypeDefaultDescription
closeButton?boolean—Figma close-button. Default: shown whenever onDismiss is set.
description?ReactNode—Figma text-secondary — body/md under the title.
display?'light' | 'dark'lightDeprecated. 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?booleantrue
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'detailedFigma 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/scrim under a 20px blur.
  • Title frame padding 20/20/0 (spacing/xl); the title binds font/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.

On this page