RestructureRequires Rework
Bottom Sheet Component link

The bottom-anchored sheet surface used for list pickers, confirmations, forms, and onboarding.

Restructure — rebuild around a content slot and delegate sheet mechanics to the platform
Three structural problems stack: (1) scope is wrong — the component is the sheet header, not the sheet; (2) the content region is 4 decorative boxes instead of a Figma Slot, so every real usage detaches or adds a new product-local variant; and (3) it overlaps with Modal (18507:71705) and Overlay (47:329691), all three independently declaring what a floating-surface-over-scrim looks like. Proposal: rebuild as EBBottomSheet — a thin wrapper around SwiftUI .sheet / Compose ModalBottomSheet — with explicit dragHandle, header, content, and footer slots, and consume the already-assessed Overlay for the scrim.
In Context

Bottom Sheet anchors to the bottom edge over a dimmed background. In the sticker-sheet context file (12522:109042), instances are used across a wide range of content shapes: ID pickers, confirmation dialogs, transfer summaries, tips lists, welcome cards, and switch-account prompts — each with different inner composition, all wrapped in the same surface.

Live Preview
Preamble here...
Title here of the header...
This area is designated for descriptions...
Label
Label
Properties (current)
alignment
preamble
description
cta
Proposed (not in Figma yet)
detent
drag handle
scrim
content
DS Health
Reusable
Fail
The component ships only the header block — every real product usage (list picker, confirmation, form, tips list) has to either detach or duplicate the surface. Content region is 4 fixed placeholder rectangles, not a slot.
Self-contained
Warn
Owns its surface bg, header colours, and CTA buttons via token bindings, but redraws the modal-surface concern already owned by the separately-maintained Overlay component. Scrim is not part of the component — consumers must add Overlay by hand every time.
Consistent
Warn
Token namespace is main/bottom-header/color/*, but the component is named Bottom Drawer — neither name matches common usage "Bottom Sheet". The Alignment axis silently changes the shape of the component (Center drops Close X, adds a headerSlot). Two axis values behave like two separate components.
Composable
Fail
Content cannot be swapped without detaching. CTAs are hard-baked Button instances (1 primary + 1 tertiary) — count and pairing are fixed. No way to compose DS Action List, Form fields, or Filter chips inside the sheet as a first-class operation.
Behavior
State iOS Android Figma Property Notes
Present / dismiss Yes Yes Not annotated iOS: .sheet(isPresented:). Android: ModalBottomSheet(onDismissRequest:). Slide-up entrance implied by pattern, not documented on the component.
Drag handle (grabber) Yes Yes Missing No handle node in Figma. iOS renders via .presentationDragIndicator(.visible). Material 3 renders via ModalBottomSheet(dragHandle = { BottomSheetDefaults.DragHandle() }).
Detent snapping (medium / large) Yes Yes Missing Sheet height in Figma is driven by content height only — no half / full axis. Natively handled via .presentationDetents([.medium, .large]) / SheetValue.PartiallyExpanded.
Swipe-down-to-dismiss Yes Yes Not annotated Platform-native gesture — should be configurable via a dismissible boolean on the wrapper.
Scrim / tap-outside dismiss Yes Yes Not composed Scrim lives in the separate Overlay component today (47:329691). Sheet should consume it, not redraw.
Close button (X) Yes Yes Asymmetric Only present on Left Align. Raster PNG. Should become a trailing slot in a title-bar region, available to both alignments.
Header slot (e.g. stepper) Yes Yes Center Align only Center Align silently adds a headerSlot used for progress bars / steppers. Left Align has no equivalent. Either surface it on both or model as its own region.
Content scroll lock N/A N/A Not documented Background scroll locked while sheet is presented; sheet's own content scrolls independently when detent < content height.
Empty / loading / error (content) Yes Yes Not modeled Content slot owner's responsibility; sheet itself has no intrinsic empty/loading/error state.
Open Issues
  • Component scope is the header, not the sheet. "Bottom Drawer" only models the rounded top surface plus a header and hard-baked CTA row. The actual sheet primitives — drag handle, detents, scrim, snap behaviour, swipe-down-to-dismiss — are absent. Every product usage in the context file (12522:109042) has to re-compose the sheet by hand. C1 · Layer Structure & Naming
  • Content region is 4 decorative placeholder rectangles, not a Slot. Inside the body, UI Slot, SLOT 2, SLOT 3, SLOT 4 are pink-dashed #FFECF8 rectangles toggled by booleans showSlot1..4. They are not Figma Slots — designers can't instance-swap in Action List rows, form fields, or Filter chips without detaching. C1 · Layer Structure & Naming
  • Alignment axis hides a second component. Left Align and Center Align are not just text-alignment differences: Left Align has an icon placeholder + title block + Close X on the right; Center Align has a separate headerSlot (used for progress bars / steppers) + title block and no Close X. These are two distinct layouts collapsed into one enum. C2 · Variant & Property Naming
  • No drag handle primitive. Nothing in the Figma tree renders a drag handle ("grabber"). iOS and Android expect this as an explicit visual affordance the user grabs to resize. Either add a handle node bound to a token, or document that rendering is delegated to the platform primitive. C5 · Interaction State Coverage
  • No detent axis. There's no medium / large / fitContent axis on the component. Sheet height is whatever the decorative slots sum to. Native APIs require a discrete detent set; the Figma model doesn't reflect this. C4 · Native Mappability
  • Scope overlap with Modal and Overlay. Bottom Sheet, Modal (18507:71705), and Overlay (47:329691) all independently model "surface above a scrim". None of them compose each other. The scrim should live in Overlay (already assessed); Modal and Bottom Sheet should consume it. C7 · Code Connect Linkability
  • Close X is raster and asymmetric. The close icon is a Figma CDN PNG asset (shape_full) rendered only on Left Align — Center Align has no dismiss affordance at all. Both alignments should offer the same affordance, and it should be a vector Icon instance bound to main/bottom-header/color/icon-close. C6 · Asset & Icon Quality
  • CTAs are hard-baked, not composable. A single primary button + a single tertiary button are instance-swapped inside the component with booleans primaryAction / secondaryAction. Consumers who need one button, two horizontal buttons, three stacked options, or a link-only footer have to detach. CTA should be a footer slot receiving any button composition. C4 · Native Mappability
  • Icon placeholder is a raw grey circle, not a Slot. Left Align's leading icon is a hardcoded #C2C6CF circle inside an icon-placeholder frame. Same anti-pattern as Modal's icon slot. Should be a Figma Slot backed by the Icon component. C1 · Layer Structure & Naming
  • Token namespace and component name disagree. The component is named "Bottom Drawer" while its tokens live in main/bottom-header/color/*. DS literature (Material, HIG) and this report use "Bottom Sheet". Pick one name and propagate: rename the component, rename the token collection, or both. C2 · Variant & Property Naming
  • No Code Connect mapping. Blocked until the restructure lands — mapping the current schema would hardcode the wrong architecture. C7 · Code Connect Linkability
Design Recommendations
  • Restructure around a clean shell with four named slots. Target shape: EBBottomSheet(isPresented, detents, dragHandle, header, content, footer). header = optional title bar region (title, preamble, leading icon, trailing icon / close). content = the one and only body Slot — accepts any DS composition (Action List, form fields, filter chips, tips list). footer = button group pinned to the bottom. Every current product usage becomes a composition: list picker = BottomSheet + Action List; confirmation = BottomSheet + description + button group; form = BottomSheet + Labeled Fields + button group; tips list = BottomSheet + numbered list; welcome card = BottomSheet + illustration + button group. Property
  • Promote the body to a real Figma Slot. Replace the 4 pink-dashed placeholder rectangles with a single named content Slot (Figma's Slot feature). Default to an empty 24-padded frame; let consumers instance-swap in Action List rows, form fields, or any other DS primitive without detaching. Slot
  • Consolidate the Bottom Sheet / Modal / Overlay family. Canonical hierarchy: Overlay (scrim primitive, already shipped) → consumed by both Modal (centered dialog) and Bottom Sheet (bottom-anchored sheet). The three ship distinct anchor positions but share the scrim. Do not collapse Modal and Bottom Sheet into one — native platforms treat them as separate APIs (.sheet vs .alert / Dialog vs ModalBottomSheet). Family
  • Replace the Alignment enum with a proper header schema. Split the silent shape-shift into explicit properties: titleAlignment = left | center (just text-align), leadingSlot (icon / avatar / empty), trailingSlot (close X / icon button / empty), aboveTitleSlot (stepper / progress bar / empty). Both alignments now share the same structural shape, just different text-align and slot content. Property
  • Add an explicit detent axis. Introduce Detent = medium | large | fitContent as a Figma variant — even if visually similar, this makes the Code Connect mapping 1:1 with .presentationDetents([.medium, .large]) / SheetValue.PartiallyExpanded. Designers can then show in mocks which detent a sheet resolves to. Property
  • Add a drag-handle primitive. Vector rect, 32×4, radius 4, bound to a new token main/bottom-header/color/drag-handle (suggest #C2C6CF). Ship as its own tiny component so Modal-style sheets can omit it and Bottom Sheet can include it. Default visible for Bottom Sheet. Asset
  • Footer action group should be a slot, not baked buttons. Replace the primaryAction + secondaryAction booleans with a footer slot that accepts any button composition — 0, 1, 2 horizontal, 2 vertical, link-only, icon+label. Same fix that Modal needs, and both should share a new EBButtonGroup primitive if the team wants to keep the DS tight. Slot
  • Replace the raster close icon with a vector instance. Swap the Figma CDN PNG close for the DS vector Close icon, and bind colour to main/bottom-header/color/icon-close (#6780A9). Available on both alignments via the trailing slot. Asset
  • Convert the leading icon-placeholder into an Icon slot. Same pattern as Modal: add a Figma Slot for the leading icon backed by the Icon component so designers can swap without detaching. Default to a neutral status icon or nothing. Slot
  • Rename the component and its token namespace. Pick one: either rename the component to Bottom Sheet and rename the token collection from main/bottom-header/color/* to main/bottom-sheet/color/*, or keep "Drawer" and align tokens to main/bottom-drawer/*. Current disagreement between component name, token name, and common DS vocabulary costs designers every time they search. Recommended: rename to Bottom Sheet to match Material / HIG / this assessment. Rename
  • Annotate the present / dismiss / gesture contract. Document on the component: slide-up entrance, fade-out-with-scrim exit, swipe-down-to-dismiss, tap-outside-dismiss, ESC/back button behaviour, focus trap, restore-focus-on-close. Developers currently have to infer these from adjacent patterns. Docs
  • Add a dismissible/modal switch. Some flows (transfer confirmation, destructive action) need a non-swipe-dismiss sheet. Surface this as dismissible: bool on the component, mapping to iOS .interactiveDismissDisabled(!dismissible) and Compose sheetState.confirmValueChange. State
Types
Default
DES DEV

A modal sheet that slides up from the bottom of the screen — typically used to confirm an action, collect a single input, or surface a focused decision without leaving the current screen.

Preamble here...
Title here of the header...
This area is designated for descriptions...
Label
Label
Properties
Alignment
Preamble
Description
CTA
Properties
Alignment Left Align
Preamble yes
Description yes
CTA Primary + Tertiary
Colors
Surface #FFFFFF
Preamble #90A8D0
Header #0A2757
Description #445C85
Close icon #6780A9
Layout
Width 360
Corner radius 8
Header padding 24 × 8
Content padding 24 sides · 32 bottom
Typography
Preamble Proxima Soft Bold · 14 / 14 · +0.25
Title Proxima Soft Bold · 22 / 26
Description BarkAda Medium · 14 / 20
Default — Colors

Bottom sheet with preamble, heading, description, optional close icon, and primary CTA.

Role Token Default
Surface bottom-header/color/bg #FFFFFF
Preamble bottom-header/color/preamble #90A8D0
Header bottom-header/color/header #0A2757
Description bottom-header/color/description #445C85
Close icon bottom-header/color/icon-close #6780A9
Property Mapping

The current Figma schema (alignment + 9 booleans) is not fit for 1:1 mapping. The table below maps the proposed post-restructure schema to native APIs.

Figma PropertySwiftUICompose
isPresented .sheet(isPresented: $binding) if (showSheet) ModalBottomSheet(onDismissRequest:)
detents .presentationDetents([.medium, .large]) sheetState = rememberModalBottomSheetState(…) + Detent enum
dragHandle = visible|hidden .presentationDragIndicator(.visible / .hidden) dragHandle = { BottomSheetDefaults.DragHandle() } or null
titleAlignment = leading|center titleAlignment: .leading / .center titleAlignment = Alignment.Start / Center
leading slot leading: { EBAvatar(…) } (ViewBuilder) leading: @Composable () -> Unit
trailing slot (e.g. close) trailing: { EBIconButton(.close) { dismiss() } } trailing: @Composable () -> Unit
aboveTitle slot (progress / stepper) aboveTitle: { EBProgressBar(…) } aboveTitle: @Composable () -> Unit
preamble preamble: String? preamble: String? = null
title title: String title: String
description description: String? description: String? = null
content slot @ViewBuilder content: () -> Content (trailing closure) content: @Composable ColumnScope.() -> Unit
footer slot footer: () -> Footer footer: @Composable RowScope.() -> Unit
dismissible .interactiveDismissDisabled(!dismissible) sheetState.confirmValueChange = { dismissible }
(legacy) alignment = Left Align → split into titleAlignment + leading + trailing → split into titleAlignment + leading + trailing
(legacy) showSlot1..4 booleans → removed, replaced by content slot → removed, replaced by content slot
(legacy) primaryAction / secondaryAction → removed, replaced by footer slot → removed, replaced by footer slot
Accessibility
RequirementiOSAndroid
Modal trait .sheet applies the modal trait automatically — VoiceOver traps focus inside the sheet. ModalBottomSheet treats content as modal by default — TalkBack swipe is contained.
Focus management Focus moves to the sheet on present; restores to trigger on dismiss. If a first-field focus is desired, use .focused($firstField). Focus enters sheet content on show; restored to trigger on dismiss. Request initial focus via LaunchedEffect + focusRequester.
Title as heading Mark the title Text with .accessibilityAddTraits(.isHeader) so VoiceOver reads it first. Use Modifier.semantics { heading() } on the title; set paneTitle on the sheet surface.
Drag handle announcement iOS's built-in grabber is announced as "Adjustable". Custom handles need .accessibilityLabel("Resize sheet") and .accessibilityAdjustableAction. Material 3 default handle exposes resize action. Custom handles need Modifier.semantics { contentDescription = "Resize sheet" }.
Dismiss gesture Swipe-down + ESC + tap-outside all route through isPresented. For non-dismissible, use .interactiveDismissDisabled(true). Back gesture + tap-outside via onDismissRequest. Non-dismissible: sheetState.confirmValueChange = { false }.
Close button (if trailing slot) Wrap 24×24 icon in a ≥44×44pt tappable area. Label: .accessibilityLabel("Close"). Wrap 24×24 icon in a ≥48×48dp tappable area. contentDescription = stringResource(R.string.close).
Destructive CTA Use role: .destructive on the footer button. Use EBButtonDefaults.destructiveColors() and explicit contentDescription.
Reduce motion Respect UIAccessibility.isReduceMotionEnabled — skip slide-up / use cross-fade. Respect Settings.Global.ANIMATOR_DURATION_SCALE — shorten animation when accessibility demands it.
Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Requires Rework Scope is the header, not the sheet. Content region is 4 decorative placeholder rectangles instead of a Figma Slot. Icon-placeholder is a raw circle. Component name ("Bottom Drawer") disagrees with token namespace ("bottom-header") and DS convention ("Bottom Sheet").
C2 Variant & Property Naming Requires Rework Alignment axis collapses two structurally different layouts (Left has leading icon + close X; Center has above-title headerSlot + no close X). No detent axis. 9 booleans (showSlot1..4, primaryAction, secondaryAction, preamble, description, iconPlaceholder) that should be slots.
C3 Token Coverage Needs Refinement Surface, preamble, header, description, close icon all bound to main/bottom-header/color/*. Icon-placeholder grey (#C2C6CF) is hardcoded. Drag-handle token doesn't exist yet (component has no handle).
C4 Native Mappability Requires Rework Does not map to .sheet / ModalBottomSheet as-is. No detent axis, no drag handle, no dismissible contract, hard-baked CTAs. After restructure → clean 1:1 mapping.
C5 Interaction State Coverage Requires Rework Only default state. No drag states (resting / dragging / snapping), no present / dismiss transition annotation, no empty / loading / error state guidance for the content slot.
C6 Asset & Icon Quality Needs Refinement Close X is a remote raster PNG (shape_full from Figma CDN) rather than a vector Icon instance. Icon-placeholder is a raw #C2C6CF circle, not a vector icon slot.
C7 Code Connect Linkability Not Mapped Blocked on restructure. Scope overlap with Modal and Overlay must be resolved; mapping the current schema would hardcode the wrong architecture.
Variants Inventory (2 total)

Single axis — Alignment = Left Align | Center Align. The header shape-shifts across these two values (see Open Issues).

#AlignmentNodeDimensionsHeader slots presentNotes
1Left Align12522:12860360 × 324iconPlaceholder (leading) · preamble · title · Close X (trailing, raster)Title + preamble left-aligned next to optional leading icon. Close X fixed top-right at (24, 24).
2Center Align12817:43834360 × 330headerSlot (above-title, e.g. progress bar) · preamble · titleNo leading icon, no close X. Adds an headerSlot used for progress bars / steppers. Title + preamble centered.
1.0.0 — April 2026Major
Initial Assessment · node 12817:43833
DS Health — 2 variants across 1 axis (alignment). Reusable and Composable both Fail: content is decorative placeholders, CTAs are hard-baked, no Slot architecture. Documented
Baseline
C1 — Component scope — Registered as "Bottom Drawer" but only models the sheet header + CTA area. Actual sheet primitives (drag handle, detents, scrim) absent. Open
C1
C1 — Content region — 4 decorative placeholder rectangles (UI Slot, SLOT 2..4) toggled by booleans instead of a Figma Slot. Open
C1
C2 — Alignment axis — Left vs Center are not just text-alignment; Center adds an above-title headerSlot and drops Close X. Two component shapes collapsed into one enum. Open
C2
C2 — Naming disagreement — Component named "Bottom Drawer", tokens in main/bottom-header/color/*, DS convention is "Bottom Sheet". Recommend rename to Bottom Sheet. Open
C2
C4 — Native mappability — No detent axis, no drag handle, hard-baked CTAs. Does not map to .sheet / ModalBottomSheet until restructure. Open
C4
C5 — Interaction states — No drag states, no empty / loading / error guidance for the content slot, no present / dismiss transition annotation. Open
C5
C6 — Raster close icon — Close X is a Figma CDN PNG (shape_full). Should be a vector Icon instance bound to main/bottom-header/color/icon-close. Open
C6
C7 — Code Connect — Blocked on restructure. Scope overlap with Modal (18507:71705) and Overlay (47:329691) must be resolved first. Open
C7
Family note — Recommended hierarchy: Overlay (scrim, shipped) → consumed by Modal (centered) + Bottom Sheet (bottom-anchored). Do not collapse Modal and Bottom Sheet; native APIs are distinct. Family
Family
Typography note — Description uses BarkAda (secondary font) at Secondary/Default/Base. Covered by the standing custom-font action item. Info
Info