To create a polished bottom sheet experience on iOS, you can use standard modal handling with the UISheetPresentationController API. This guide walks through how to do that in NativeScript, including how to make the background transparent on iOS 26+ so system glass can show through.
This guide shows two implementations:
- Classic NativeScript (TypeScript + XML)
- Angular (
NativeDialog) - All frameworks (Solid, Svelte and Vue) can use this same approach as the core logic is in the modal handling.
Core requirements
- Present modal as PageSheet on iOS.
- Access the presented view controller and configure UISheetPresentationController.
- On iOS 26+, make the modal background transparent so system glass can show through.
Example A: Classic NativeScript (TypeScript + XML)
Open the modal (page.showModal)
import { EventData, Page, ShowModalOptions } from '@nativescript/core'
export function openProfileSheet(args: EventData) {
const page = args.object as Page
const options: ShowModalOptions = {
context: {
name: 'Ada Lovelace',
nationality: 'British',
achievements: ['First computer programmer', 'Analytical Engine notes'],
},
closeCallback() {},
fullscreen: __ANDROID__,
ios: {
presentationStyle: UIModalPresentationStyle.PageSheet,
},
}
page.showModal('~/modal/profile-sheet', options)
}Configure the sheet in the modal code-behind
import {
Color,
EventData,
fromObject,
ShownModallyData,
Utils,
} from '@nativescript/core'
function setContext(page: any, modalContext: any) {
if (!modalContext || page.bindingContext) return
page.bindingContext = fromObject({
...modalContext,
onClose() {
page.closeModal()
},
})
}
function configureIOSSheet(page: any) {
if (!__IOS__) return
const vc = page.ios || page.viewController
const sheet =
vc?.sheetPresentationController ||
vc?.parentViewController?.sheetPresentationController
if (!sheet) return
vc.modalPresentationStyle = UIModalPresentationStyle.PageSheet
sheet.detents = Utils.ios.collections.jsArrayToNSArray([
UISheetPresentationControllerDetent.mediumDetent(),
UISheetPresentationControllerDetent.largeDetent(),
])
sheet.selectedDetentIdentifier =
UISheetPresentationControllerDetentIdentifierMedium
sheet.prefersGrabberVisible = true
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.preferredCornerRadius = 20
if (Utils.SDK_VERSION >= 26) {
page.backgroundColor = new Color('transparent')
if (vc?.view) {
vc.view.backgroundColor = UIColor.clearColor
}
}
}
export function onLoaded(args: EventData) {
const page = args.object as any
setContext(page, page._modalContext)
configureIOSSheet(page)
}
export function onShownModally(args: ShownModallyData) {
setContext(args.object, args.context)
}Modal View
<GridLayout rows="auto,*" class="bg-transparent" loaded="onLoaded" shownModally="onShownModally">
<GridLayout row="0" columns="*,auto" class="mx-3 mt-3 px-5 py-4 border border-gray-200 rounded-2xl bg-white/80">
<Label text="Scientist Profile" class="text-xs uppercase tracking-wide text-gray-500" />
<Label col="1" text="✕" class="text-xl text-gray-500 px-2" tap="onClose" />
</GridLayout>
<StackLayout row="1" class="p-5">
<Label text="{{ name }}" class="text-2xl font-semibold text-gray-900 mb-2" />
<Label text="{{ nationality }}" class="text-base text-gray-700 mb-4" />
<Button text="Close" tap="onClose" class="bg-indigo-600 text-white rounded-xl py-3" />
</StackLayout>
</GridLayout>Example B: Angular (NativeDialog)
Open with PageSheet presentationStyle
this.dialog.open(SheetComponent, {
data: {
page: this.page,
person: this.personService.getPerson(id),
},
nativeOptions: {
fullscreen: __ANDROID__,
ios: {
presentationStyle: UIModalPresentationStyle.PageSheet,
},
},
})Configure sheet
onLoaded(args: EventData) {
const view = args.object as View;
this.configureSheet(view);
setTimeout(() => this.configureSheet(view), 0);
}
private configureSheet(view: View) {
if (!__IOS__) return;
const sourcePage = this.data.page;
const nativeModalRef = (this.dialogRef as any)?._nativeModalRef;
const modalView = nativeModalRef?.modalViewRef?.firstNativeLikeView as View | undefined;
const vc =
modalView?.viewController ||
view?.viewController ||
view?.page?.ios ||
sourcePage?.ios?.presentedViewController ||
sourcePage?.ios ||
sourcePage?.viewController;
if (!vc) return;
vc.modalPresentationStyle = UIModalPresentationStyle.PageSheet;
const sheet = vc.sheetPresentationController || vc.parentViewController?.sheetPresentationController;
if (!sheet) return;
sheet.detents = Utils.ios.collections.jsArrayToNSArray([
UISheetPresentationControllerDetent.mediumDetent(),
UISheetPresentationControllerDetent.largeDetent(),
]);
sheet.selectedDetentIdentifier = UISheetPresentationControllerDetentIdentifierMedium;
sheet.prefersScrollingExpandsWhenScrolledToEdge = false;
sheet.prefersEdgeAttachedInCompactHeight = false;
sheet.prefersGrabberVisible = true;
sheet.preferredCornerRadius = 20;
if (Utils.SDK_VERSION >= 26) {
const modalPage = modalView?.page || view?.page;
if (modalPage) modalPage.backgroundColor = new Color('transparent');
if (vc.view) vc.view.backgroundColor = UIColor.clearColor;
}
}View template notes
Use a transparent root container and translucent cards:
- Root:
bg-transparent - Content surfaces:
bg-white/75,bg-white/80
This lets system material remain visible behind your content.
Troubleshooting checklist
If it opens like a classic modal, check these first:
- iOS open options include
presentationStyle: UIModalPresentationStyle.PageSheet. sheetPresentationControlleris non-null on the controller you configure.- You’re configuring the presented controller (not only the source page).
- iOS 26+ transparency is applied to modal page/view controller background.
Final result
With this setup, you get the modern iOS sheet behavior in NativeScript:
- medium/large detents,
- native grabber,
- rounded top corners,
- glass-friendly visual treatment.
