Skip to content
WebView Native UIdocs

Animation

These gesture and animation helpers are production-ready and optional. The theme works without them.

Sheet of Glass with ion-tab-button / ion-segment-button

Register an ion-tab-bar or ion-segment element to add a moving selection effect to its buttons.

registerTabBarEffect respects prefers-reduced-motion, including changes while the page is open. Reduced motion removes the moving lens and tab scaling while preserving standard Ionic selection. Turning it off restores the optional effect until the registration is destroyed.

registerSegmentEffect adds only a visual layer; Ionic still owns selection, dragging, keyboard handling, and events. An unselected segment starts its lens movement on release, while a selected segment expands in place, including on short taps. The motion follows iOS 27 presentation-layer measurements. Reduced motion skips this optional effect; native glass refraction is approximated with CSS.

Sheet of Glass animation on ion-tab-button and ion-segment-button

import { registerTabBarEffect, registerSegmentEffect } from '@rdlabo/ionic-theme-ios27';

/**
 * Register initialized Ionic DOM elements.
 */
const tabBar = document.querySelector<HTMLElement>('ion-tab-bar');
const segment = document.querySelector<HTMLElement>('ion-segment');
const registeredTabBarEffect = tabBar ? registerTabBarEffect(tabBar) : undefined;
const registeredSegmentEffect = segment ? registerSegmentEffect(segment) : undefined;

const destroy = () => {
  /**
   * If the registered DOM element is removed (e.g., due to page navigation),
   * make sure to destroy the gesture and animation. This will also remove the event listeners.
   * You can re-register them if needed.
   */
  registeredTabBarEffect?.destroy();
  registeredSegmentEffect?.destroy();
};

Searchbar cancel icon

Ionic 8 and 9 render cancelButtonIcon only in Material Design mode. Register a searchbar to use that same property in iOS mode. Without this optional helper, the theme keeps Ionic's cancel text button.

This is a temporary rendering shim, scheduled for removal once the supported Ionic versions render cancelButtonIcon in the iOS DOM. It skips insertion when an icon is already present. It does not change keyboard handling, focus, accessibility attributes on the button, or cancel/clear behavior; these remain Ionic's responsibility. The CSS design and animations are independent of the helper and also apply to an icon rendered by Ionic itself.

import { supportSeachbarCancelButtonIcon } from '@rdlabo/ionic-theme-ios27';
import { closeOutline } from 'ionicons/icons';

const searchbar = document.querySelector<HTMLIonSearchbarElement>('ion-searchbar')!;
searchbar.animated = true;
searchbar.showCancelButton = 'focus'; // 'always' and 'never' also work
searchbar.cancelButtonIcon = closeOutline;
searchbar.clearIcon = closeOutline;
searchbar.cancelButtonText = 'Close search'; // accessible name of the icon button
const effect = supportSeachbarCancelButtonIcon(searchbar);

// After changing cancelButtonIcon as a JavaScript property, call effect.refresh().
// When the component/page is destroyed:
// effect.destroy();

Registration and exclusions

Register after the Ionic element has initialized (for example, Angular's ngAfterViewInit).

Condition Behavior
Supported iOS-mode searchbar Preserves Ionic's button and event handlers; destroy() restores text content.
MD mode, searchbar-classic, ios-theme-disabled or ios26-disabled Helper does not apply.
animated / prefers-reduced-motion Respected by the CSS animation.
CSS customization --background, --box-shadow, --border-radius and --cancel-button-color remain available.

Clear and cancel buttons

Button Ionic appearance settings Action
Clear clearIcon, showClearButton, --clear-button-color Clears while retaining input focus.
Cancel cancelButtonIcon, --cancel-button-color Ends the search.

Both can appear together beside the input. Set clearIcon and cancelButtonIcon to the same icon for matching appearance.

Both external glass buttons use a 44px resting size and expand while pressed. Reduced motion disables scaling. The clear button appears outside the text field; Ionic supplies the icons.

While held, the glass background becomes opaque and the icon opacity is 0.55, preserving its configured color. Use --background-activated to customize the pressed background independently of --background.

TabBarSearchable: Searchable with ion-tab-bar and ion-fab-button

Use the following structure inside ion-tabs to animate a search button into a search toolbar.

TabBarSearchable animation expanding search from ion-fab-button into the tab bar

<ion-content>...</ion-content>
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
  <ion-fab-button aria-label="Search" (click)="present($event)">
    <ion-icon name="search"></ion-icon>
  </ion-fab-button>
</ion-fab>
<ion-footer [translucent]="true">
  <ion-toolbar>
    <ion-buttons slot="start">
      <!-- ion-icon name is set dynamically by the animation -->
      <ion-button fill="default"><ion-icon slot="icon-only"></ion-icon> </ion-button>
    </ion-buttons>
    <!-- User set `ionChange` or other events. -->
    <ion-searchbar aria-label="Search" (ionChange)="example($event)"></ion-searchbar>
  </ion-toolbar>
</ion-footer>
import { attachTabBarSearchable, TabBarSearchableType } from '@rdlabo/ionic-theme-ios27';
import type { TabBarSearchableFunction } from '@rdlabo/ionic-theme-ios27';

let searchableFun: TabBarSearchableFunction | undefined;
const initialize = () => {
  // attachTabBarSearchable has state. You should initialize per page.
  const tabBar = document.querySelector<HTMLElement>('ion-tab-bar');
  const fabButton = document.querySelector<HTMLElement>('ion-fab-button');
  const footer = document.querySelector<HTMLElement>('ion-footer');
  if (!tabBar || !fabButton || !footer) {
    return;
  }
  searchableFun = attachTabBarSearchable(tabBar, fabButton, footer);
};

const present = (event: Event) => {
  searchableFun!(event, TabBarSearchableType.Enter);
};

const dismiss = (event: Event) => {
  searchableFun!(event, TabBarSearchableType.Leave);
};