Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ce78459
fix: align toolbar button geometry with system controls
rdlabo Oct 3, 2026
945135d
fix: preserve toolbar hit areas and opt-out spacing
rdlabo Oct 3, 2026
f48c844
fix: retain full-size close icons in toolbar buttons
rdlabo Oct 3, 2026
2166f4f
fix: retain intrinsic width for single toolbar icons
rdlabo Oct 3, 2026
3822579
refactor: defer redundant toolbar styling to Ionic defaults
rdlabo Oct 3, 2026
250cce8
chore: update E2E screenshots
github-actions[bot] Oct 3, 2026
9d584f6
fix: preserve toolbar opt-out spacing and back icon sizing
rdlabo Oct 3, 2026
41961d7
chore: update E2E screenshots
github-actions[bot] Oct 3, 2026
5c47966
chore: drop imperceptible screenshot changes
rdlabo Oct 3, 2026
722a04c
fix: preserve standalone and mixed toolbar button geometry
rdlabo Oct 3, 2026
ac95c76
chore: update E2E screenshots
github-actions[bot] Oct 3, 2026
fec6e8c
fix: exclude hidden back buttons from toolbar spacing
rdlabo Oct 3, 2026
639a6a0
fix: align grouped toolbar spacing and visible slot boundaries
rdlabo Oct 3, 2026
c8bd858
chore: update E2E screenshots
github-actions[bot] Oct 3, 2026
956f4c3
docs: note toolbar back-button class removal in v2.0.0
rdlabo Oct 3, 2026
1c45e56
fix: exclude empty toolbar groups from action spacing
rdlabo Oct 3, 2026
d81c6cf
chore: update E2E screenshots
github-actions[bot] Oct 3, 2026
3cf5192
chore: remove imperceptible segment snapshot change
rdlabo Oct 3, 2026
d71c60c
fix: preserve search spacing and modal safe areas
rdlabo Oct 3, 2026
4298c2d
fix: keep centered card modal headers at a 10px inset
devin-ai-integration[bot] Oct 4, 2026
dc83f89
chore: update searchbar E2E screenshots
devin-ai-integration[bot] Oct 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/button-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-flat.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/duo-split-menu-half-open.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/modal-card-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/modal-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/modal-normal-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/modal-normal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/modal-sheet-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/modal-sheet.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/searchbar-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/searchbar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/select-modal-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/select-modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/toolbar-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified demo/e2e/screenshot.spec.ts-snapshots/toolbar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 79 additions & 0 deletions demo/src/app/index/pages/toolbar/toolbar.page.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,85 @@ <h2>toolbar</h2>
<ion-title>Menu Button</ion-title>
</ion-toolbar>

<ion-list [inset]="true">
<ion-item-group>
<ion-item>
<ion-label>
<h2>Toolbar spacing</h2>
<p>Compare the outer inset and spacing between visible actions.</p>
</ion-label>
</ion-item>
</ion-item-group>
</ion-list>

@for (slot of ['start', 'end']; track slot) {
<ion-item lines="none"
><ion-label>{{ slot }}: single group</ion-label></ion-item
>
<ion-toolbar>
<ion-buttons [slot]="slot"><ion-button>Save</ion-button></ion-buttons>
</ion-toolbar>

<ion-item lines="none"
><ion-label>{{ slot }}: two groups</ion-label></ion-item
>
<ion-toolbar>
<ion-buttons [slot]="slot"><ion-button>Save</ion-button></ion-buttons>
<ion-buttons [slot]="slot"><ion-button>Edit</ion-button></ion-buttons>
</ion-toolbar>

<ion-item lines="none"
><ion-label>{{ slot }}: standalone button and group</ion-label></ion-item
>
<ion-toolbar>
<ion-button [slot]="slot" fill="clear">Save</ion-button>
<ion-buttons [slot]="slot"><ion-button>Edit</ion-button></ion-buttons>
</ion-toolbar>

<ion-item lines="none"
><ion-label>{{ slot }}: hidden menu group and visible group</ion-label></ion-item
>
<ion-toolbar>
<ion-buttons [slot]="slot"><ion-menu-button menu="toolbar-spacing-missing-menu"></ion-menu-button></ion-buttons>
<ion-buttons [slot]="slot"><ion-button>Save</ion-button></ion-buttons>
</ion-toolbar>

<ion-item lines="none"
><ion-label>{{ slot }}: hidden child and visible child</ion-label></ion-item
>
<ion-toolbar>
<ion-buttons [slot]="slot">
<ion-menu-button menu="toolbar-spacing-missing-menu"></ion-menu-button>
<ion-button>Save</ion-button>
</ion-buttons>
<ion-buttons [slot]="slot"><ion-button>Edit</ion-button></ion-buttons>
</ion-toolbar>

<ion-item lines="none"
><ion-label>{{ slot }}: empty group between visible groups</ion-label></ion-item
>
<ion-toolbar>
<ion-buttons [slot]="slot"><ion-button>Save</ion-button></ion-buttons>
<ion-buttons [slot]="slot"></ion-buttons>
<ion-buttons [slot]="slot"><ion-button>Edit</ion-button></ion-buttons>
</ion-toolbar>
}
@for (
slots of [
['start', 'secondary'],
['primary', 'end'],
];
track slots[0]
) {
<ion-item lines="none"
><ion-label>{{ slots[0] }} / {{ slots[1] }}</ion-label></ion-item
>
<ion-toolbar>
<ion-buttons [slot]="slots[0]"><ion-button>Save</ion-button></ion-buttons>
<ion-buttons [slot]="slots[1]"><ion-button>Edit</ion-button></ion-buttons>
</ion-toolbar>
}

<ion-toolbar>
<ion-searchbar></ion-searchbar>
</ion-toolbar>
Expand Down
47 changes: 47 additions & 0 deletions src/styles/components/ion-button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
69 changes: 62 additions & 7 deletions src/styles/components/ion-toolbar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 モーダルの上余白は端末種別ではなく画面寸法で切り替わる

幅768px以上かつ高さ600px以上なら上余白は0px、それ以外は6pxです。横向きや分割表示で想定する配置を確認してください。

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

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;
}
}
}
Expand Down Expand Up @@ -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;
}
}
}
}
Loading