Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
584acd2
feat(header,footer,tab-bar): add scrollEffect=hide cross-platform scr…
OS-susmitabhowmik Jul 7, 2026
02cdca8
fix(header,footer,tab-bar): restore accidentally removed props and co…
OS-susmitabhowmik Jul 9, 2026
f14e78e
refactor(header,footer,tab-bar): improve scroll effect code readability
OS-susmitabhowmik Jul 9, 2026
8fdd497
fix(header,footer,tab-bar): remove theme restrictions
OS-susmitabhowmik Jul 9, 2026
42e044d
fix(tab-bar): fix compact scroll-effect-hide animating diagonally
OS-susmitabhowmik Jul 9, 2026
07a60ca
fix(header): scope banner role toggling to iOS mode only
OS-susmitabhowmik Jul 9, 2026
4a25aa1
test(header,footer,tab-bar): add scroll-effect-hide e2e tests and enf…
OS-susmitabhowmik Jul 9, 2026
9e1932b
fix(tab-bar): fix scroll-effect-hide tests
OS-susmitabhowmik Jul 9, 2026
1aa051a
fix(header,footer): restrict collapse prop effects to ios, allow scro…
OS-susmitabhowmik Jul 9, 2026
9ad2c05
test(header,footer): fix tests
OS-susmitabhowmik Jul 9, 2026
11ea3b3
refactor(header,footer,tab-bar): extract scroll-hide logic into share…
OS-susmitabhowmik Jul 10, 2026
4f407ca
fix(tab-bar): add aria-hidden for hiding on scroll
OS-susmitabhowmik Jul 13, 2026
8f8fa2d
chore(): fix stylelint errors
OS-susmitabhowmik Jul 13, 2026
cbab4fd
chore(): fix linter errors
OS-susmitabhowmik Jul 13, 2026
6dbad44
refactor(header): clean up condense header lookup and collapse class …
OS-susmitabhowmik Jul 13, 2026
d405790
feat(header,footer): add fade/condense support for all themes
OS-susmitabhowmik Jul 15, 2026
ecf9447
fix(header,footer,tab-bar): compose appRootSelector from config value
OS-susmitabhowmik Jul 15, 2026
d69e17c
fix(header,footer,tab-bar): guard setupScrollEffectHide against overl…
OS-susmitabhowmik Jul 15, 2026
7a34aba
fix(header,tab-bar): fix scrollEffect resolution and tab-bar-in-foote…
OS-susmitabhowmik Jul 15, 2026
2718712
fix(header,footer,tab-bar): dedupe deprecation warnings and use consi…
OS-susmitabhowmik Jul 15, 2026
104bdd3
refactor(header,footer,tab-bar): converge hide state on declarative @…
OS-susmitabhowmik Jul 15, 2026
d1c1c45
docs(scroll-hide-controller): add comments explaining wheel/scroll du…
OS-susmitabhowmik Jul 15, 2026
73b8302
chore(): lint
OS-susmitabhowmik Jul 15, 2026
a18abe0
Merge branch 'next' into ROU-12870-scroll-effect
OS-susmitabhowmik Jul 15, 2026
87d45d8
fix(header,footer): gate condense/fade classes when using deprecated …
OS-susmitabhowmik Jul 15, 2026
51f4925
fix(header): hide deprecated collapse=condense header on non-iOS themes
OS-susmitabhowmik Jul 15, 2026
8a9e15e
fix(header): scope condense-inactive box-shadow removal to md only
OS-susmitabhowmik Jul 15, 2026
6c7217e
fix(header): re-assert condense border override in iOS stylesheet
OS-susmitabhowmik Jul 15, 2026
ff4ea8f
chore(): lint
OS-susmitabhowmik Jul 15, 2026
c1b8da8
fix(header,footer,tab-bar): restore imperative hide state
OS-susmitabhowmik Jul 16, 2026
34c991c
fix(header): hide deprecated collapse=condense header on non-iOS themes
OS-susmitabhowmik Jul 16, 2026
3b045eb
chore(): lint
OS-susmitabhowmik Jul 16, 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
4 changes: 3 additions & 1 deletion core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,7 @@ ion-fab-list,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-footer,none
ion-footer,prop,collapse,"fade" | undefined,undefined,false,false
ion-footer,prop,mode,"ios" | "md",undefined,false,false
ion-footer,prop,scrollEffect,"fade" | "hide" | undefined,undefined,false,false
ion-footer,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-footer,prop,translucent,boolean,false,false,false

Expand Down Expand Up @@ -937,6 +938,7 @@ ion-header,none
ion-header,prop,collapse,"condense" | "fade" | undefined,undefined,false,false
ion-header,prop,divider,boolean,false,false,false
ion-header,prop,mode,"ios" | "md",undefined,false,false
ion-header,prop,scrollEffect,"condense" | "fade" | "hide" | undefined,undefined,false,false
ion-header,prop,theme,"ios" | "md" | "ionic",undefined,false,false
ion-header,prop,translucent,boolean,false,false,false

Expand Down Expand Up @@ -2445,8 +2447,8 @@ ion-tab,method,setActive,setActive() => Promise<void>
ion-tab-bar,shadow
ion-tab-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
ion-tab-bar,prop,expand,"compact" | "full",'full',false,false
ion-tab-bar,prop,hideOnScroll,boolean,false,false,false
ion-tab-bar,prop,mode,"ios" | "md",undefined,false,false
ion-tab-bar,prop,scrollEffect,"hide" | undefined,undefined,false,false
ion-tab-bar,prop,selectedTab,string | undefined,undefined,false,false
ion-tab-bar,prop,shape,"rectangular" | "round" | "soft" | undefined,undefined,false,false
ion-tab-bar,prop,theme,"ios" | "md" | "ionic",undefined,false,false
Expand Down
50 changes: 37 additions & 13 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/bre
import { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface";
import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface";
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimeParts, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
import { FooterScrollEffect } from "./components/footer/footer-interface";
import { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface";
import { HeaderScrollEffect } from "./components/header/header-interface";
import { SpinnerTypes } from "./components/spinner/spinner-configs";
import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
import { InputOtpChangeEventDetail, InputOtpCompleteEventDetail, InputOtpInputEventDetail } from "./components/input-otp/input-otp-interface";
Expand All @@ -40,7 +42,7 @@ import { SegmentViewScrollEvent } from "./components/segment-view/segment-view-i
import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
import { SelectModalOption } from "./components/select-modal/select-modal-interface";
import { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
import { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
import { TabBarChangedEventDetail, TabBarScrollEffect, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
import { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
import { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface";
import { ToggleChangeEventDetail } from "./components/toggle/toggle-interface";
Expand All @@ -55,7 +57,9 @@ export { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/bre
export { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface";
export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-interface";
export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimeParts, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
export { FooterScrollEffect } from "./components/footer/footer-interface";
export { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface";
export { HeaderScrollEffect } from "./components/header/header-interface";
export { SpinnerTypes } from "./components/spinner/spinner-configs";
export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
export { InputOtpChangeEventDetail, InputOtpCompleteEventDetail, InputOtpInputEventDetail } from "./components/input-otp/input-otp-interface";
Expand All @@ -79,7 +83,7 @@ export { SegmentViewScrollEvent } from "./components/segment-view/segment-view-i
export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
export { SelectModalOption } from "./components/select-modal/select-modal-interface";
export { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
export { TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
export { TabBarChangedEventDetail, TabBarScrollEffect, TabButtonClickEventDetail, TabButtonLayout } from "./components/tab-bar/tab-bar-interface";
export { TextareaChangeEventDetail, TextareaInputEventDetail } from "./components/textarea/textarea-interface";
export { ToastButton, ToastDismissOptions, ToastLayout, ToastPosition, ToastPresentOptions, ToastSwipeGestureDirection } from "./components/toast/toast-interface";
export { ToggleChangeEventDetail } from "./components/toggle/toggle-interface";
Expand Down Expand Up @@ -1459,12 +1463,17 @@ export namespace Components {
interface IonFooter {
/**
* Describes the scroll effect that will be applied to the footer. Only applies when the theme is `"ios"`.
* @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'fade';
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling up. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: FooterScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -1536,6 +1545,7 @@ export namespace Components {
interface IonHeader {
/**
* Describes the scroll effect that will be applied to the header. Only applies when the theme is `"ios"`. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
* @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'condense' | 'fade';
/**
Expand All @@ -1547,6 +1557,10 @@ export namespace Components {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling up. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: HeaderScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -4041,15 +4055,14 @@ export namespace Components {
* @default 'full'
*/
"expand": 'compact' | 'full';
/**
* If `true`, the tab bar will be hidden when the user scrolls down and shown when the user scrolls up. Only applies when the theme is `"ionic"` and `expand` is `"compact"`.
* @default false
*/
"hideOnScroll": boolean;
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling up.
*/
"scrollEffect"?: TabBarScrollEffect;
/**
* The selected tab component
*/
Expand Down Expand Up @@ -7536,12 +7549,17 @@ declare namespace LocalJSX {
interface IonFooter {
/**
* Describes the scroll effect that will be applied to the footer. Only applies when the theme is `"ios"`.
* @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'fade';
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the footer. `"hide"` slides the footer out of view when scrolling down and back in when scrolling up. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: FooterScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -7609,6 +7627,7 @@ declare namespace LocalJSX {
interface IonHeader {
/**
* Describes the scroll effect that will be applied to the header. Only applies when the theme is `"ios"`. Typically used for [Collapsible Large Titles](https://ionicframework.com/docs/api/title#collapsible-large-titles)
* @deprecated Use `scrollEffect` instead.
*/
"collapse"?: 'condense' | 'fade';
/**
Expand All @@ -7620,6 +7639,10 @@ declare namespace LocalJSX {
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
/**
* Describes the scroll effect that will be applied to the header. `"hide"` slides the header out of view when scrolling down and back in when scrolling up. `"condense"` collapses the large title into the main toolbar on scroll. `"fade"` fades the toolbar background on scroll.
*/
"scrollEffect"?: HeaderScrollEffect;
/**
* The theme determines the visual appearance of the component.
*/
Expand Down Expand Up @@ -10208,17 +10231,16 @@ declare namespace LocalJSX {
* @default 'full'
*/
"expand"?: 'compact' | 'full';
/**
* If `true`, the tab bar will be hidden when the user scrolls down and shown when the user scrolls up. Only applies when the theme is `"ionic"` and `expand` is `"compact"`.
* @default false
*/
"hideOnScroll"?: boolean;
/**
* The mode determines the platform behaviors of the component.
*/
"mode"?: "ios" | "md";
"onIonTabBarChanged"?: (event: IonTabBarCustomEvent<TabBarChangedEventDetail>) => void;
"onIonTabBarLoaded"?: (event: IonTabBarCustomEvent<void>) => void;
/**
* Describes the scroll effect that will be applied to the tab bar. `"hide"` slides the tab bar out of view when scrolling down and back in when scrolling up.
*/
"scrollEffect"?: TabBarScrollEffect;
/**
* The selected tab component
*/
Expand Down Expand Up @@ -11015,6 +11037,7 @@ declare namespace LocalJSX {
"side": 'start' | 'end' | 'top' | 'bottom';
}
interface IonFooterAttributes {
"scrollEffect": FooterScrollEffect;
"collapse": 'fade';
"translucent": boolean;
}
Expand All @@ -11028,6 +11051,7 @@ declare namespace LocalJSX {
"fixed": boolean;
}
interface IonHeaderAttributes {
"scrollEffect": HeaderScrollEffect;
"collapse": 'condense' | 'fade';
"divider": boolean;
"translucent": boolean;
Expand Down Expand Up @@ -11465,7 +11489,7 @@ declare namespace LocalJSX {
interface IonTabBarAttributes {
"color": Color;
"selectedTab": string;
"hideOnScroll": boolean;
"scrollEffect": TabBarScrollEffect;
"translucent": boolean;
"expand": 'compact' | 'full';
"shape": 'soft' | 'round' | 'rectangular';
Expand Down
55 changes: 55 additions & 0 deletions core/src/components/content/content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,58 @@
*/
transform: translateZ(0);
}

// Content: Header/Footer/Tab Bar Scroll Effect Hide — Gap Compensation
// --------------------------------------------------
// When a bar hides via scrollEffect="hide", it stays in the layout flow
// but is translated out of view. The inner-scroll must shift and expand
// to fill the gap so no empty space is visible to the user.

// Header gap compensation — only when content is NOT fullscreen.
// When fullscreen=true, inner-scroll already fills the full viewport
// (offset-top = 0), so translateY would over-shift the content.
:host(.content-header-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll {
transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), height 300ms cubic-bezier(0, 0, 0.2, 1);
}

:host(.content-header-hide-scroll-partner:not(.content-fullscreen).content-header-hide-scroll-hidden) .inner-scroll {
height: calc(100% + var(--internal-header-hide-height, 0px));

transform: translateY(calc(-1 * var(--internal-header-hide-height, 0px)));

transition: transform 200ms cubic-bezier(0.4, 0, 1, 1), height 200ms cubic-bezier(0.4, 0, 1, 1);
}

// Footer gap compensation — only when content is NOT fullscreen
:host(.content-footer-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll {
transition: height 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

:host(.content-footer-hide-scroll-partner:not(.content-fullscreen).content-footer-hide-scroll-hidden) .inner-scroll {
height: calc(100% + var(--internal-footer-hide-height, 0px));

transition: height 350ms cubic-bezier(0.4, 0, 1, 1);
}

// Tab bar gap compensation — only when content is NOT fullscreen
:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll {
transition: height 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen).content-tab-bar-hide-scroll-hidden) .inner-scroll {
height: calc(100% + var(--internal-tab-bar-hide-height, 0px));

transition: height 350ms cubic-bezier(0.4, 0, 1, 1);
}

@media (prefers-reduced-motion: reduce) {
:host(.content-header-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll,
:host(.content-header-hide-scroll-partner:not(.content-fullscreen).content-header-hide-scroll-hidden) .inner-scroll,
:host(.content-footer-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll,
:host(.content-footer-hide-scroll-partner:not(.content-fullscreen).content-footer-hide-scroll-hidden) .inner-scroll,
:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen)) .inner-scroll,
:host(.content-tab-bar-hide-scroll-partner:not(.content-fullscreen).content-tab-bar-hide-scroll-hidden)
.inner-scroll {
transition: none;
}
}
1 change: 1 addition & 0 deletions core/src/components/footer/footer-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type FooterScrollEffect = 'hide' | 'fade';
7 changes: 0 additions & 7 deletions core/src/components/footer/footer.ios.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,3 @@
.footer-ios.ion-no-border ion-toolbar:first-of-type {
--border-width: 0;
}

// iOS Footer - Collapse Fade
// --------------------------------------------------

.footer-collapse-fade ion-toolbar {
--opacity-scale: inherit;
}
9 changes: 9 additions & 0 deletions core/src/components/footer/footer.md.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
box-shadow: $footer-md-box-shadow;
}

/**
* The fade effect fades the toolbar background via --opacity-scale,
* but box-shadow is not affected by that variable. Remove it so
* the shadow doesn't persist when the background is faded out.
*/
.footer-md.footer-collapse-fade {
box-shadow: none;
}

.footer-md.ion-no-border {
box-shadow: none;
}
31 changes: 31 additions & 0 deletions core/src/components/footer/footer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,34 @@ ion-footer {
ion-footer.footer-toolbar-padding ion-toolbar:last-of-type {
padding-bottom: var(--ion-safe-area-bottom, 0);
}

// Footer - Collapse Fade
// --------------------------------------------------

.footer-collapse-fade ion-toolbar {
--opacity-scale: inherit;
}

// Footer - Scroll Effect Hide
// --------------------------------------------------

// Slide back into view when scrolling up — spring ease-out snaps the footer
// back into place quickly and settles smoothly.
ion-footer.footer-scroll-effect-hide {
transition: transform 200ms cubic-bezier(0.16, 1, 0.3, 1);
}

// Slide out of view when scrolling down — footer moves off the bottom of the
// screen. Slower duration mirrors the natural pace of the downward scroll.
ion-footer.footer-scroll-effect-hide.footer-scroll-hidden {
transform: translateY(100%);

transition: transform 350ms cubic-bezier(0.4, 0, 1, 1);
}

@media (prefers-reduced-motion: reduce) {
ion-footer.footer-scroll-effect-hide,
ion-footer.footer-scroll-effect-hide.footer-scroll-hidden {
transition: none;
}
}
Loading
Loading