Skip to content

feat(modal): support Capacitor SystemBars plugin for card modal status bar style - #31584

Draft
Chuckytuh wants to merge 2 commits into
ionic-team:mainfrom
Chuckytuh:capacitor-systembars
Draft

Chuckytuh wants to merge 2 commits into
ionic-team:mainfrom
Chuckytuh:capacitor-systembars

Conversation

@Chuckytuh

@Chuckytuh Chuckytuh commented Oct 16, 2026 •

Copy link
Copy Markdown

Issue number: N/A. There is no linked issue; this PR is a starting point for discussion.


What is the current behavior?

On iOS, the card modal (ios mode, viewport narrower than 768px) changes the status bar style only when the app has installed @capacitor/status-bar. Capacitor 8 apps that don't install it get no status bar change when a card modal opens or closes.

What is the new behavior?

  • The internal status bar helper picks a plugin in this order: @capacitor/status-bar if it is available, otherwise Capacitor's built-in SystemBars plugin (Capacitor 8+), otherwise none (web, Cordova, older Capacitor). Card modal code is unchanged.
  • With SystemBars, the helper sets the style with { style, bar: 'StatusBar' }, so the navigation/gesture bar is never restyled. SystemBars cannot report the current style, so on dismiss the status bar goes back to DEFAULT (it follows the device appearance).
  • SystemBars types come from a type-only import of @capacitor/core, and the runtime code uses string literals, so the build output does not import @capacitor/core at runtime. @capacitor/status-bar becomes optional and no dependencies are added.
  • New spec tests cover the helper's current behavior, the SystemBars-only case, and the case where both plugins are available (only StatusBar is called).

Does this introduce a breaking change?

  • Yes
  • No

Other information

  • The core build, lint and full spec suite pass.
  • StatusBar.getEngine() (internal) can now return either plugin. Nothing in core/src calls it.

Simulator check

I ran this on an iPhone 17 Pro simulator (Xcode 26.6, light appearance) with a minimal Capacitor 8 app built against this branch's core/dist. @capacitor/status-bar was not installed (StatusBar=false SystemBars=true). The test page wraps SystemBars.setStyle and prints every call on screen, so each video shows the payload alongside the status bar. Runs 1–3 are automated and each starts from a fresh app launch.

1. Open the card modal, then close it with the button

Opening sends {"style":"DARK","bar":"StatusBar"} and the status bar text turns white over the dark backdrop. Closing sends {"style":"DEFAULT","bar":"StatusBar"} and the bar returns to the device default.

1-present-dismiss.mp4

2. Swipe-to-close, cancelled

A slow drag pulls the card about a third of the way down, then it snaps back. No calls are made and the bar stays white.

2-swipe-cancel.mp4

3. Swipe-to-close, completed

The bar returns to default as the drag passes the halfway point, before the modal finishes closing. The log shows three DEFAULT calls: one when the drag passes halfway and two from the modal's existing dismiss path. That's the same sequence the StatusBar plugin receives today, and repeating the style has no visible effect.

3-swipe-complete.mp4

4. Swipe-to-close: drag past halfway and back up in the same touch (recorded by hand)

Opening sends DARK. Dragging down past the halfway point sends {"style":"DEFAULT","bar":"StatusBar"} and the status bar text turns dark. Without lifting, dragging back above halfway sends {"style":"DARK","bar":"StatusBar"} and the text turns white again. On release the card snaps back, the bar stays white, and there's no didDismiss. The end of the clip also shows a second swipe that closes the modal, which matches run 3.

4-swipe-cross-back.mp4

Every call included bar: 'StatusBar', so the navigation/gesture bar was never restyled.

Simulator check: with @capacitor/status-bar installed

Same simulator, a second test app with @capacitor/status-bar@8.0.4 installed, so both plugins are available (StatusBar=true SystemBars=true). The page wraps setStyle on both plugins and logs StatusBar.getInfo() at startup and after each close. At startup it sets the style to LIGHT, which is not the default. The simulator is in dark appearance, so LIGHT (dark text) looks different from DEFAULT (white text in dark appearance). That shows the exact previous style coming back rather than a fallback to DEFAULT. Runs 1–3 are automated and each starts from a fresh app launch.

1. Open the card modal, then close it with the button

Opening sends StatusBar.setStyle {"style":"DARK"} and the text turns white. Closing sends StatusBar.setStyle {"style":"LIGHT"}, the text turns dark again, and getInfo reports LIGHT.

sb-1-present-dismiss.mp4

2. Swipe-to-close, cancelled

The drag goes about a third of the way down and the card snaps back. No calls are made and the bar stays white.

sb-2-swipe-cancel.mp4

3. Swipe-to-close, completed

The style goes back to LIGHT as the drag passes halfway (three {"style":"LIGHT"} calls, matching the SystemBars run). After close, getInfo reports LIGHT.

sb-3-swipe-complete.mp4

SystemBars.setStyle was never called in any of these runs: when the StatusBar plugin is available, it is the only one used, and payloads are unchanged from before this PR ({ style } only).

Add spec tests for the internal status bar wrapper that the iOS card
modal uses. They drive the wrapper against a fake window.Capacitor and
assert which plugin receives which call and what getStyle resolves to:

- StatusBar plugin available: setStyle forwards exactly { style },
  getStyle returns the plugin's getInfo().style.
- Capacitor present without the plugin, or the plugin registered but
  not available: setStyle calls nothing, getStyle resolves to DEFAULT.
- No window.Capacitor: setStyle does not throw, getStyle resolves to
  DEFAULT.

No production code changes.
…s bar style

The native status bar wrapper now resolves its engine in order: the
@capacitor/status-bar plugin if available, else Capacitor's built-in
SystemBars plugin (Capacitor 8+), else none.

On the SystemBars path, setStyle targets only the status bar
({ style, bar: 'StatusBar' }) and getStyle resolves to DEFAULT, since
SystemBars has no getter. SystemBars types come from a type-only import;
runtime code uses string literals, so no runtime @capacitor/core import
is added. Card modal code is unchanged.
@Chuckytuh
Chuckytuh requested a review from a team as a code owner October 16, 2026 19:04
@Chuckytuh
Chuckytuh requested a review from ShaneK October 16, 2026 19:04
@vercel

vercel Bot commented Oct 16, 2026

Copy link
Copy Markdown

@Chuckytuh is attempting to deploy a commit to the Ionic Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions Bot added the package: core @ionic/core package label Oct 16, 2026
@Chuckytuh
Chuckytuh marked this pull request as draft October 16, 2026 19:05

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant