Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
230 changes: 18 additions & 212 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

A CSS/JS theme library that applies iOS26 design system to Ionic applications.

**Documentation:** [Read the full documentation](https://docs.rdlabo.dev/projects/ionic-theme-ios26)

![](screenshots/ios26.png)
![iOS 26 themed Ionic screens with Liquid Glass tab bar, lists, and controls](https://raw.githubusercontent.com/rdlabo-dev/ionic-theme-ios26/v2.3.2/screenshots/ios26.png)

DEMO is here: https://ionic-theme-ios26.netlify.app/

Expand All @@ -16,7 +14,12 @@ I'm also working on the Android Design (Material Design 3) theme. Be sure to cat

👉️[rdlabo-dev/ionic-theme-md3](https://github.com/rdlabo-dev/ionic-theme-md3)

## Setup

## Quick start

After [Installation](#installation), import the theme CSS. Details are in Installation below.

## Installation

This is a CSS theme for extending your Ionic project. It does not work on its own, so use it together with the Ionic Framework.

Expand All @@ -42,7 +45,7 @@ And import the theme in your project's main CSS file (e.g., `src/styles.scss`).

/**
* If you will use the design of ion-item-group with ion-list on Android as well, import it.
* More info: https://github.com/rdlabo-dev/ionic-theme-ios26/blob/main/USING_ION_ITEM_GROUP.md
* More info: ./docs/using-ion-item-group.md
* Note: This is include `@rdlabo/ionic-theme-md3`
* @import '@rdlabo/ionic-theme-ios26/dist/css/md-ion-list-inset.css';
*/
Expand Down Expand Up @@ -88,217 +91,19 @@ createApp(App)
})
```

## Important Notes

### Using `ion-item-group`

Under specific conditions, you need to use `ion-item-group`.

```diff
<ion-list inset=true>
<ion-list-header><ion-label>Label</ion-label></ion-list-header>
+ <ion-item-group>
<ion-item>...</ion-item>
<ion-item>...</ion-item>
+ </ion-item-group>
</ion-list>
```

For details, please refer to [USING_ION_ITEM_GROUP.md](./USING_ION_ITEM_GROUP.md).


## Features

### CSS Variables

To customize the library's default styles to match your design, several CSS variables are provided. See this file for details:
https://github.com/rdlabo-dev/ionic-theme-ios26/blob/main/src/styles/default-variables.scss

### `.ios26-disabled` Class

Add the `.ios26-disabled` class to disable the iOS26 theme on specific components.

```html
<!-- iOS26 theme applied -->
<ion-button>iOS26 Design</ion-button>

<!-- Standard Ionic iOS styling -->
<ion-button class="ios26-disabled">Standard Ionic Design</ion-button>
```

### Liquid Glass Mixin

Import the SCSS files from the main package to use the liquid glass mixin.

```scss
@use '@rdlabo/ionic-theme-ios26/src/styles/utils/api.scss';

ion-textarea label.textarea-wrapper {
@include api.glass-background;
}
```

### Additional Design

To achieve higher fidelity to iOS26 design, you can implement additional design provided by this library. For more details, please visit:

https://ionic-theme-ios26.netlify.app/main/docs


## Experimental Animation

__This feature is experimental. The library can be used without this feature.__


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

By registering `ion-tab-bar` / `ion-segment`, you can display animation effects on `ion-tab-button` / `ion-segment-button`

[![Image from Gyazo](https://i.gyazo.com/fafd726b520827f042c76b6c73abd81c.gif)](https://gyazo.com/fafd726b520827f042c76b6c73abd81c)

```js
import { registerTabBarEffect, registerSegmentEffect } from '@rdlabo/ionic-theme-ios26';

/**
* Register DOM elements. Effects are applied using Ionic Gesture and Ionic Animation.
*/
const registeredTabBarEffect = registerTabBarEffect(document.querySelector<HTMLElement>('ion-tab-bar'));
const registeredSegmentEffect = registerSegmentEffect(document.querySelector<HTMLElement>('ion-segment'));

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();
}
```

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

Enable Searchable for the DOM structure with the specified markup inner `ion-tabs`.

[![Image from Gyazo](https://i.gyazo.com/06bc63f4a474f9f19f5b1d865f5c2a85.gif)](https://gyazo.com/06bc63f4a474f9f19f5b1d865f5c2a85)


```html
<ion-content>...</ion-content>
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
<ion-fab-button (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 (ionChange)="example($event)"></ion-searchbar>
</ion-toolbar>
</ion-footer>
```

```ts
import { attachTabBarSearchable, TabBarSearchableFunction, TabBarSearchableType } from '@rdlabo/ionic-theme-ios26';

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

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

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

## Documentation

## Additional Information
Start with [Installation](#installation), then [Using ion-item-group](./docs/using-ion-item-group.md) when you use inset lists.

### How to prevent loading a theme file on iOS 18

If you want to load a theme file only when the user's device is running iOS 26 (and let users on iOS 18 use the default Ionic iOS theme), you can achieve this by adding a supports-condition to your `import`.

```css
@import '@rdlabo/ionic-theme-ios26/dist/css/default-variables.css' supports(text-wrap: pretty);
@import '@rdlabo/ionic-theme-ios26/dist/css/ionic-theme-ios26.css' supports(text-wrap: pretty);
@import '@rdlabo/ionic-theme-ios26/dist/css/md-remove-ios-class-effect.css' supports(text-wrap: pretty);
@import '@rdlabo/ionic-theme-ios26/dist/css/md-ion-list-inset.css' supports(text-wrap: pretty);
```
- [Using ion-item-group](./docs/using-ion-item-group.md) — required markup for inset lists.
- [Features](./docs/features.md) — CSS variables, `.ios26-disabled`, liquid glass.
- [Experimental Animation](./docs/experimental-animation.md) — tab bar and searchable effects.
- [iOS 18](./docs/ios-18.md) — load the theme only on iOS 26.
- [Migration](./docs/migration.md) — selective component imports and dark mode.


## Migration Support

For gradual migration, you can selectively apply the iOS26 theme by importing individual components instead of the full theme file.

```css
@import '@rdlabo/ionic-theme-ios26/dist/css/utils/translucent';
@import '@rdlabo/ionic-theme-ios26/dist/css/components/ion-action-sheet';
@import '@rdlabo/ionic-theme-ios26/dist/css/components/ion-alert';
@import '@rdlabo/ionic-theme-ios26/dist/css/components/ion-breadcrumbs';
@import '@rdlabo/ionic-theme-ios26/dist/css/components/ion-button';
...
```

### Dark Mode with Individual Components

When importing individual components with dark mode support, use SCSS instead of CSS. This is because the selectors differ between `Always`, `System`, and `Class` modes.

> **Note**: Currently, only `ion-button` has separate dark mode styling applied.

Always (Always Dark Mode):
```scss
@use '@rdlabo/ionic-theme-ios26/src/styles/utils/theme-dark';

:root {
@include theme-dark.default-variables;
}
@include theme-dark.ion-button;
@include theme-dark.ion-fab;
@include theme-dark.ion-tabs;
@include theme-dark.ion-segment;
```

System (Follow System Settings):
```scss
@use '@rdlabo/ionic-theme-ios26/src/styles/utils/theme-dark';

@media (prefers-color-scheme: dark) {
:root {
@include theme-dark.default-variables;
}
@include theme-dark.ion-button;
@include theme-dark.ion-fab;
@include theme-dark.ion-tabs;
@include theme-dark.ion-segment;
}
```

Class (Toggle with CSS Class):
```scss
@use '@rdlabo/ionic-theme-ios26/src/styles/utils/theme-dark';

.ion-palette-dark {
@include theme-dark.default-variables;
@include theme-dark.ion-button;
@include theme-dark.ion-fab;
@include theme-dark.ion-tabs;
@include theme-dark.ion-segment;
}
```
<!-- rdlabo-docs-omit -->
**Full documentation:** [https://docs.rdlabo.dev/projects/ionic-theme-ios26](https://docs.rdlabo.dev/projects/ionic-theme-ios26)

## Development & Testing

Expand Down Expand Up @@ -333,3 +138,4 @@ npm run test:e2e:debug
# Update baseline screenshots (when intentionally changing UI)
npm run test:e2e:update
```
<!-- /rdlabo-docs-omit -->
57 changes: 1 addition & 56 deletions USING_ION_ITEM_GROUP.md
Original file line number Diff line number Diff line change
@@ -1,56 +1 @@
# Using `ion-item-group` with iOS26 Theme

This theme aims to bring Ionic Framework applications as close as possible to iOS 26 design. In most cases, you can use your existing Ionic code as-is, but **only under specific conditions**, you need to add `ion-item-group`.

## When is `ion-item-group` required?

It is only required when **both** of the following conditions are met:

- You have enabled the `inset` property on `ion-list`

Only when these conditions apply, you need to wrap your list items with `ion-item-group`.

## Implementation Example

```diff
<ion-list inset=true>
<ion-list-header><ion-label>Label</ion-label></ion-list-header>
+ <ion-item-group>
<ion-item>...</ion-item>
<ion-item>...</ion-item>
+ </ion-item-group>
</ion-list>
```

## Why is this change necessary?

### Background: Challenges in iOS Design Reproduction

By default in Ionic Framework, `ion-list` has a background color, and `ion-list-header` is treated as part of the list. However, with this structure, it's impossible to accurately reproduce **iOS's native design patterns**.

![](screenshots/why-ion-list-inset.png)

### Solution: Background Color Separation

To faithfully reproduce iOS design, this theme makes the following changes:

- Set `ion-list` background color to transparent
- Delegate background color to `ion-item-group`

This change allows `ion-list-header` to be treated as an independent element, achieving the native iOS appearance.

## Using the Same Design with Material Design

If you want to use the same design pattern with Material Design theme, import the following CSS:

```css
@import '@rdlabo/ionic-theme-ios26/dist/css/md-ion-list-inset.css';
```

This will apply the same `ion-item-group` pattern to the Material Design theme as well.

## Summary

- **Most cases**: You can use your existing Ionic code as-is
- **Specific conditions only**: `ion-item-group` is only required when setting using `inset` on `ion-list`
- **Purpose**: To accurately reproduce iOS 26's native design patterns
This guide now lives in [docs/using-ion-item-group.md](./docs/using-ion-item-group.md).
Loading
Loading