diff --git a/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png
index 3e7037ef..5e19d2c2 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/button-dark.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/button.png b/demo/e2e/screenshot.spec.ts-snapshots/button.png
index 741d8030..0e53ffc6 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/button.png and b/demo/e2e/screenshot.spec.ts-snapshots/button.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png
index 04733a20..c792bea8 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png and b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png
index aadb0d0b..2373615d 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png and b/demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png
index de307a2e..e13227a5 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png
index 74fe686d..fc977782 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-card.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png
index befc076c..7d79d04a 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png
index 8c1b81ef..796432a1 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png
index fcd10c9f..3b12557a 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png
index 6dc36fe9..43bbf18c 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png and b/demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png
index 00abd4c9..b59dce09 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png b/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png
index 86f632ae..23ac6adb 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png and b/demo/e2e/screenshot.spec.ts-snapshots/searchbar.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png
index b31e7d03..2449510c 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png b/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png
index 3b715bb7..33281ace 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png and b/demo/e2e/screenshot.spec.ts-snapshots/select-modal.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png b/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png
index 944345a7..cb5f1509 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png and b/demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png b/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png
index 33a147cc..bfb4eae4 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png and b/demo/e2e/screenshot.spec.ts-snapshots/toolbar.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png
index 4781def7..a91b2767 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png and b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-ltr.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png
index 9b5f4615..42f5626b 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png and b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-back-button-rtl.png differ
diff --git a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png
index aeaabbac..0886e70d 100644
Binary files a/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png and b/demo/e2e/screenshot.spec.ts-snapshots/vertical-bars-toolbar-actions.png differ
diff --git a/demo/src/app/index/pages/toolbar/toolbar.page.html b/demo/src/app/index/pages/toolbar/toolbar.page.html
index 910d0187..c5d623af 100644
--- a/demo/src/app/index/pages/toolbar/toolbar.page.html
+++ b/demo/src/app/index/pages/toolbar/toolbar.page.html
@@ -106,6 +106,85 @@
toolbar
Menu Button
+
+
+
+
+ Toolbar spacing
+ Compare the outer inset and spacing between visible actions.
+
+
+
+
+
+ @for (slot of ['start', 'end']; track slot) {
+ {{ slot }}: single group
+
+ Save
+
+
+ {{ slot }}: two groups
+
+ Save
+ Edit
+
+
+ {{ slot }}: standalone button and group
+
+ Save
+ Edit
+
+
+ {{ slot }}: hidden menu group and visible group
+
+
+ Save
+
+
+ {{ slot }}: hidden child and visible child
+
+
+
+ Save
+
+ Edit
+
+
+ {{ slot }}: empty group between visible groups
+
+ Save
+
+ Edit
+
+ }
+ @for (
+ slots of [
+ ['start', 'secondary'],
+ ['primary', 'end'],
+ ];
+ track slots[0]
+ ) {
+ {{ slots[0] }} / {{ slots[1] }}
+
+ Save
+ Edit
+
+ }
+
diff --git a/src/styles/components/ion-button.scss b/src/styles/components/ion-button.scss
index 5a0cacfb..7de71e36 100644
--- a/src/styles/components/ion-button.scss
+++ b/src/styles/components/ion-button.scss
@@ -436,3 +436,50 @@ ion-button.ios:not(.ios-theme-disabled, .ios26-disabled) {
ion-back-button.ios:not(.ios-theme-disabled, .ios26-disabled) {
@include theme-button($is-back-button: true);
}
+
+// Toolbar button typography and spacing follow system toolbar controls.
+ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled) {
+ ion-button.ios:not(.ios-theme-disabled, .ios26-disabled):not(.button-small, .button-large):not(
+ :where(ion-buttons.ios-theme-disabled *, ion-buttons.ios26-disabled *)
+ ) {
+ font-size: 1rem;
+ font-weight: 400;
+ // TODO(v2.0.0): Remove .back-button-has-icon-only; Ionic only adds it to ion-back-button.
+ &:not(.button-has-icon-only, .back-button-has-icon-only) {
+ --padding-start: 16px;
+ --padding-end: 16px;
+ }
+ }
+
+ ion-buttons.ios:not(.ios-theme-disabled, .ios26-disabled):has(ion-button ~ ion-button):not(
+ :has(
+ ion-back-button,
+ ion-button:not(.button-clear),
+ ion-menu-button,
+ .ios-theme-disabled,
+ .ios26-disabled,
+ .button-small,
+ .button-large
+ )
+ ) {
+ height: 44px;
+ padding-inline: 3.5px;
+ // SwiftUI toolbar items share glass with 16pt between their inner controls.
+ gap: 16px;
+
+ ion-button.ios:not(.ios-theme-disabled, .ios26-disabled).button-clear {
+ // Keep the action's hit area through the group's height, while the
+ // inner plain control follows the system's 36px visual layout.
+ height: 100%;
+ display: inline-flex;
+ align-items: center;
+ --padding-start: 7px;
+ --padding-end: 7px;
+ &::part(native) {
+ width: auto;
+ height: 36px;
+ min-height: 36px;
+ }
+ }
+ }
+}
diff --git a/src/styles/components/ion-toolbar.scss b/src/styles/components/ion-toolbar.scss
index 96576690..1a93a9c2 100644
--- a/src/styles/components/ion-toolbar.scss
+++ b/src/styles/components/ion-toolbar.scss
@@ -13,22 +13,27 @@ ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar:not(.ios-th
ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-card,
ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-sheet {
ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type {
- padding-top: 0;
+ // The system's modal safe area places the 44px control 16px below the
+ // content edge, with or without a sheet drag indicator.
+ padding-top: 6px;
padding-bottom: 0;
--min-height: 64px;
- --padding-start: 3px;
- --padding-end: 3px;
}
}
@media (min-width: 768px) and (min-height: 600px) {
- ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled) {
+ ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled):not(.modal-sheet) {
ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type {
- padding-top: 0;
+ // Center a 44px control at 10px, retaining the safe area in fullscreen modals.
+ padding-top: max(calc(var(--ion-safe-area-top, 0px) - 10px), 0px);
padding-bottom: 0;
--min-height: 64px;
- --padding-start: 3px;
- --padding-end: 3px;
+ }
+ }
+ // Ionic keeps the root safe area in card modals, including centered dialogs.
+ ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-card {
+ ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type {
+ padding-top: 0;
}
}
}
@@ -106,4 +111,54 @@ ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled) {
ion-buttons > ion-back-button {
margin-inline: 0;
}
+
+ // Keep the outer inset for individual actions too. Adjacent actions in the
+ // same slot share a 12px gap rather than adding two outer margins together.
+ > ion-button:not(.ios-theme-disabled, .ios26-disabled) {
+ margin-inline: 12px;
+ }
+ // Rail sources leave toolbar layout; ordinary native projection retains it.
+ $visible: ':not([hidden], .ios-theme-native-ui-shell-prehidden, [data-native-ui-shell][data-native-ui-shell-vertical-bars], ion-menu-button.menu-button-hidden, ion-back-button:not(.show-back-button))';
+ $button: ':is(ion-button, ion-back-button)#{$visible}';
+ $group: 'ion-buttons#{$visible}';
+ $action: ':is(#{$button}, #{$group}:has(> #{$visible}))';
+ $optOut: ':is(.ios-theme-disabled, .ios26-disabled)';
+
+ > ion-buttons:not(:has(> #{$visible})) {
+ display: none;
+ }
+
+ @each $slot in start, end, primary, secondary {
+ // Keep standalone margins when the slot includes a visible opted-out action.
+ &:not(.toolbar-searchbar):not(:has(> #{$button}#{$optOut}[slot='#{$slot}'], > #{$group}#{$optOut}[slot='#{$slot}'] > #{$visible})) {
+ > #{$action}[slot='#{$slot}'] {
+ margin-inline: 6px;
+ }
+ > :nth-child(1 of #{$action}[slot='#{$slot}']) {
+ margin-inline-start: 12px;
+ }
+ > :nth-last-child(1 of #{$action}[slot='#{$slot}']) {
+ margin-inline-end: 12px;
+ }
+ }
+ }
+ // Ionic orders these slots together, independently of their DOM order.
+ @each $first, $second in (start, secondary), (primary, end) {
+ &:not(.toolbar-searchbar):has(> #{$button}[slot='#{$first}'], > #{$group}[slot='#{$first}'] > #{$visible}):has(
+ > #{$button}[slot='#{$second}'],
+ > #{$group}[slot='#{$second}'] > #{$visible}
+ ):not(
+ :has(
+ > #{$button}#{$optOut}:is([slot='#{$first}'], [slot='#{$second}']),
+ > #{$group}#{$optOut}:is([slot='#{$first}'], [slot='#{$second}']) > #{$visible}
+ )
+ ) {
+ > :nth-last-child(1 of #{$action}[slot='#{$first}']) {
+ margin-inline-end: 6px;
+ }
+ > :nth-child(1 of #{$action}[slot='#{$second}']) {
+ margin-inline-start: 6px;
+ }
+ }
+ }
}