From 2a8ae4ba6c294bedb7ed4d75fb2fa38559e7e8a1 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 2 Nov 2026 15:30:47 -0800 Subject: [PATCH 1/3] feat(backdrop): add recipe and tokens --- BREAKING.md | 11 ++ core/api.txt | 2 +- core/src/components.d.ts | 8 -- .../components/backdrop/_backdrop.docs.scss | 3 + .../components/backdrop/backdrop.interface.ts | 3 + .../{backdrop.common.scss => backdrop.scss} | 12 +- core/src/components/backdrop/backdrop.tsx | 17 +-- core/src/themes/common.globals.scss | 1 + core/src/themes/ionic/default.tokens.ts | 6 +- core/src/themes/ios/default.tokens.ts | 4 + core/src/themes/md/default.tokens.ts | 4 + core/src/themes/themes.interfaces.ts | 2 + core/src/utils/test/theme.spec.ts | 104 ++++++++++++++++++ core/src/utils/theme.ts | 85 ++++++++++++-- docs/sass-guidelines.md | 1 - .../angular/src/lazy/directives/proxies.ts | 4 +- .../src/standalone/directives/ion-backdrop.ts | 4 +- 17 files changed, 234 insertions(+), 37 deletions(-) create mode 100644 core/src/components/backdrop/_backdrop.docs.scss create mode 100644 core/src/components/backdrop/backdrop.interface.ts rename core/src/components/backdrop/{backdrop.common.scss => backdrop.scss} (51%) diff --git a/BREAKING.md b/BREAKING.md index c14d44c0a57..db526998fd2 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -17,6 +17,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver - [Global Styles](#version-10x-global-styles) - [Components](#version-10x-components) + - [Backdrop](#version-10x-backdrop) - [Button](#version-10x-button) - [Card](#version-10x-card) - [Checkbox](#version-10x-checkbox) @@ -51,6 +52,16 @@ This is a comprehensive list of the breaking changes introduced in the major ver

Components

+

Backdrop

+ +The following breaking changes apply to `ion-backdrop`: + +1. Theme classes (`ion-backdrop.md`, `ion-backdrop.ios`) are no longer supported. + +
Theme classes
+ +Remove any instances that target the theme classes: `ion-backdrop.md`, `ion-backdrop.ios`. +

Button

- The `border-radius` of the `ios` and `md` button now defaults to `6px` and `999px` instead of `14px` and `4px`, respectively, in accordance with the iOS and Material Design 3 guidelines. To revert to the previous appearance, set the `shape` to `"soft"` for `md` and override the `--border-radius` CSS variable for `ios` to `14px`, or set it to a different value entirely. diff --git a/core/api.txt b/core/api.txt index 10968f62024..608cc3499f0 100644 --- a/core/api.txt +++ b/core/api.txt @@ -308,9 +308,9 @@ ion-backdrop,shadow ion-backdrop,prop,mode,"ios" | "md",undefined,false,false ion-backdrop,prop,stopPropagation,boolean,true,false,false ion-backdrop,prop,tappable,boolean,true,false,false -ion-backdrop,prop,theme,"ios" | "md" | "ionic",undefined,false,false ion-backdrop,prop,visible,boolean,true,false,false ion-backdrop,event,ionBackdropTap,void,true +ion-backdrop,css-prop,--ion-backdrop-background ion-badge,shadow ion-badge,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true diff --git a/core/src/components.d.ts b/core/src/components.d.ts index cbb2706dc77..87132769896 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -469,10 +469,6 @@ export namespace Components { * @default true */ "tappable": boolean; - /** - * The theme determines the visual appearance of the component. - */ - "theme"?: "ios" | "md" | "ionic"; /** * If `true`, the backdrop will be visible. * @default true @@ -6316,10 +6312,6 @@ declare namespace LocalJSX { * @default true */ "tappable"?: boolean; - /** - * The theme determines the visual appearance of the component. - */ - "theme"?: "ios" | "md" | "ionic"; /** * If `true`, the backdrop will be visible. * @default true diff --git a/core/src/components/backdrop/_backdrop.docs.scss b/core/src/components/backdrop/_backdrop.docs.scss new file mode 100644 index 00000000000..43d21a687c6 --- /dev/null +++ b/core/src/components/backdrop/_backdrop.docs.scss @@ -0,0 +1,3 @@ +/** + * @prop --ion-backdrop-background: Background of the backdrop + */ diff --git a/core/src/components/backdrop/backdrop.interface.ts b/core/src/components/backdrop/backdrop.interface.ts new file mode 100644 index 00000000000..eb1a8eab277 --- /dev/null +++ b/core/src/components/backdrop/backdrop.interface.ts @@ -0,0 +1,3 @@ +export interface IonBackdropRecipe { + background?: string; +} diff --git a/core/src/components/backdrop/backdrop.common.scss b/core/src/components/backdrop/backdrop.scss similarity index 51% rename from core/src/components/backdrop/backdrop.common.scss rename to core/src/components/backdrop/backdrop.scss index 1230dc367a2..8806e034396 100644 --- a/core/src/components/backdrop/backdrop.common.scss +++ b/core/src/components/backdrop/backdrop.scss @@ -1,20 +1,28 @@ -@import "../../themes/mixins.scss"; +@use "../../themes/common.globals" as globals; +@use "../../themes/mixins" as mixins; +@use "./backdrop.docs"; // Backdrop // -------------------------------------------------- :host { - @include position(0, 0, 0, 0); + @include mixins.position(0, 0, 0, 0); display: block; position: absolute; transform: translateZ(0); + background-color: var(--ion-backdrop-background); + contain: strict; cursor: pointer; + + // Nearly transparent until an overlay or menu animation fades it in opacity: 0.01; touch-action: none; + + z-index: globals.$z-index-backdrop; } :host(.backdrop-hide) { diff --git a/core/src/components/backdrop/backdrop.tsx b/core/src/components/backdrop/backdrop.tsx index 2a1d883cbdb..7064b58157b 100644 --- a/core/src/components/backdrop/backdrop.tsx +++ b/core/src/components/backdrop/backdrop.tsx @@ -1,19 +1,12 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { Component, Event, Host, Listen, Prop, h } from '@stencil/core'; -import { getIonTheme } from '../../global/ionic-global'; - /** * @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component. - * @virtualProp {"ios" | "md" | "ionic"} theme - The theme determines the visual appearance of the component. */ @Component({ tag: 'ion-backdrop', - styleUrls: { - ios: 'backdrop.ios.scss', - md: 'backdrop.md.scss', - ionic: 'backdrop.ionic.scss', - }, + styleUrl: 'backdrop.scss', shadow: true, }) export class Backdrop implements ComponentInterface { @@ -53,14 +46,14 @@ export class Backdrop implements ComponentInterface { } render() { - const theme = getIonTheme(this); + const { tappable, visible } = this; + return ( ); diff --git a/core/src/themes/common.globals.scss b/core/src/themes/common.globals.scss index 02ca084f482..c997077a75c 100644 --- a/core/src/themes/common.globals.scss +++ b/core/src/themes/common.globals.scss @@ -20,3 +20,4 @@ $screen-breakpoints: ( // Z-Index // -------------------------------------------------- $z-index-page-container: 0; +$z-index-backdrop: 2; diff --git a/core/src/themes/ionic/default.tokens.ts b/core/src/themes/ionic/default.tokens.ts index ce168adb25c..b4d52d0bfb9 100644 --- a/core/src/themes/ionic/default.tokens.ts +++ b/core/src/themes/ionic/default.tokens.ts @@ -1,4 +1,4 @@ -import { currentColor, mix, dynamicFont, ionColor } from '../../utils/theme'; +import { rgba, currentColor, mix, dynamicFont, ionColor } from '../../utils/theme'; import { defaultTheme as baseDefaultTheme } from '../base/default.tokens'; import { colors as baseColors } from '../base/shared.tokens'; import type { DefaultTheme } from '../themes.interfaces'; @@ -113,6 +113,10 @@ export const defaultTheme: DefaultTheme = { }, components: { + IonBackdrop: { + background: rgba('19, 21, 24', 0.7), + }, + IonBadge: { // Hues hue: { diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts index dc0cf5b5712..79a695305f1 100644 --- a/core/src/themes/ios/default.tokens.ts +++ b/core/src/themes/ios/default.tokens.ts @@ -123,6 +123,10 @@ export const defaultTheme: DefaultTheme = { }, components: { + IonBackdrop: { + background: `var(--ion-backdrop-color, ${baseColors.black})`, + }, + IonBadge: { font: { family: 'var(--ion-font-family)', diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts index 06da2f5e884..01ef34afd12 100644 --- a/core/src/themes/md/default.tokens.ts +++ b/core/src/themes/md/default.tokens.ts @@ -117,6 +117,10 @@ export const defaultTheme: DefaultTheme = { }, components: { + IonBackdrop: { + background: `var(--ion-backdrop-color, ${baseColors.black})`, + }, + IonBadge: { font: { family: 'var(--ion-font-family)', diff --git a/core/src/themes/themes.interfaces.ts b/core/src/themes/themes.interfaces.ts index b1b77c7ebad..e0e2c468e1f 100644 --- a/core/src/themes/themes.interfaces.ts +++ b/core/src/themes/themes.interfaces.ts @@ -1,3 +1,4 @@ +import type { IonBackdropRecipe } from '../components/backdrop/backdrop.interface'; import type { IonBadgeConfig, IonBadgeRecipe } from '../components/badge/badge.interfaces'; import type { IonChipConfig, IonChipRecipe } from '../components/chip/chip.interfaces'; import type { IonColRecipe } from '../components/col/col.interface'; @@ -297,6 +298,7 @@ export type DefaultTheme = BaseTheme & { type Components = { IonAvatar?: any; + IonBackdrop?: IonBackdropRecipe; IonBadge?: IonBadgeRecipe; IonChip?: IonChipRecipe; IonCol?: IonColRecipe; diff --git a/core/src/utils/test/theme.spec.ts b/core/src/utils/test/theme.spec.ts index 03a8ffb1183..929d0efc9a5 100644 --- a/core/src/utils/test/theme.spec.ts +++ b/core/src/utils/test/theme.spec.ts @@ -1,8 +1,13 @@ import { newSpecPage } from '@stencil/core/testing'; +import { readdirSync, readFileSync } from 'fs'; +import { join } from 'path'; import { Buttons } from '../../components/buttons/buttons'; import { CardContent } from '../../components/card-content/card-content'; import { Chip } from '../../components/chip/chip'; +import { defaultTheme as ionicTheme } from '../../themes/ionic/default.tokens'; +import { defaultTheme as iosTheme } from '../../themes/ios/default.tokens'; +import { defaultTheme as mdTheme } from '../../themes/md/default.tokens'; import { config } from '@global/config'; import { SCREEN_BREAKPOINT_NAMES, resetScreenBreakpoints } from '../breakpoints'; @@ -13,6 +18,7 @@ import { generateCSSVars, generateGlobalThemeCSS, getClassList, + INTERNAL_CHILD_COMPONENTS, getClassMap, getCustomTheme, hexToRgb, @@ -846,6 +852,104 @@ describe('generateComponentsThemeCSS', () => { expect(css).toBe(''); }); + + it('should add the parents that render a child internally to its selectors', () => { + const components = { + IonBackdrop: { + background: 'red', + }, + }; + + const css = generateComponentsThemeCSS(components).replace(/\s/g, ''); + + const expectedCSS = ` + ion-backdrop, + ion-menu, + ion-modal, + ion-popover, + ion-datetime { + --ion-backdrop-background: red; + } + `.replace(/\s/g, ''); + + expect(css).toBe(expectedCSS); + }); +}); + +describe('INTERNAL_CHILD_COMPONENTS', () => { + const COMPONENTS_DIR = join(__dirname, '../../components'); + + const toPascalCase = (tag: string) => + tag + .split('-') + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(''); + + const isChild = (key: string) => /^Ion[A-Z]/.test(key); + + /** + * Migrated components have tokens of their own in at least one theme. Entries + * that only nest children, directly or under `semantic`, are skipped. + */ + const migratedComponents = [ + ...new Set( + [iosTheme, mdTheme, ionicTheme].flatMap((theme) => + Object.entries(theme.components ?? {}) + .filter(([, tokens]) => + Object.entries(tokens ?? {}).some(([key, value]) => + key === 'semantic' ? Object.keys(value ?? {}).some((semanticKey) => !isChild(semanticKey)) : !isChild(key) + ) + ) + .map(([name]) => name) + ) + ), + ]; + + /** + * The ripple's only token falls back to the same value in its stylesheet, + * so it renders correctly inside a shadow root without being listed. + */ + const exempt = ['IonRippleEffect']; + + /** + * Token CSS can't reach a migrated child that a parent renders in its own shadow + * root, like a modal's backdrop, unless the parent is in `INTERNAL_CHILD_COMPONENTS`. + * The same goes for a listed host rendered in a shadow root, like datetime's popover. + * A missing entry fails silently, so this reads each shadow component's TSX. + */ + it('should list every migrated component rendered in a shadow root', () => { + const missing: string[] = []; + + for (const dir of readdirSync(COMPONENTS_DIR)) { + const file = join(COMPONENTS_DIR, dir, `${dir}.tsx`); + let source: string; + try { + source = readFileSync(file, 'utf8'); + } catch { + continue; + } + + if (!source.includes('shadow: true')) { + continue; + } + + const parent = toPascalCase(`ion-${dir}`); + const renderedTags = new Set([...source.matchAll(/<(ion-[a-z-]+)[\s>/]/g)].map((match) => match[1])); + + for (const tag of renderedTags) { + const child = toPascalCase(tag); + const isHost = child in INTERNAL_CHILD_COMPONENTS; + if (child === parent || exempt.includes(child) || (!migratedComponents.includes(child) && !isHost)) { + continue; + } + if (!INTERNAL_CHILD_COMPONENTS[parent]?.includes(child)) { + missing.push(`${parent} renders ${child}`); + } + } + } + + expect(missing).toEqual([]); + }); }); describe('generateBreakpointCSS', () => { diff --git a/core/src/utils/theme.ts b/core/src/utils/theme.ts index ea94cd64649..6b97903784b 100644 --- a/core/src/utils/theme.ts +++ b/core/src/utils/theme.ts @@ -518,20 +518,89 @@ export const generateComponentsThemeCSS = (components: any): string => { for (const [component, componentTokens] of Object.entries(components)) { const componentTag = convertToKebabCase(component); - const vars = generateCSSVars(componentTokens, `--${componentTag}-`); - const componentBlock = ` - ${componentTag} { - ${vars} - } - `; - - css += componentBlock; + /** + * The component's own tokens, set on its tag and on any parent that renders + * it inside a shadow root (refer to `INTERNAL_CHILD_COMPONENTS`): + * `ion-backdrop, ion-menu, ion-modal, ion-popover { --ion-backdrop-*: … }`. + */ + const selectors = [componentTag, ...getInternalRenderHosts(component)]; + css += generateBlock(selectors, componentTokens as Record, componentTag); } return css; }; +/** + * Components that render a child component inside their own shadow root. + * + * Token CSS is added to the document head, so its selectors can't reach an + * element inside a shadow root: `ion-backdrop { … }` styles every `ion-backdrop` + * in the page except the one that `ion-modal` renders itself. + * Custom properties do inherit into a shadow root, though, so each host listed + * here joins the child's selectors and passes the tokens down: + * + * ```css + * ion-backdrop, + * ion-menu, + * ion-modal, + * ion-popover { + * --ion-backdrop-background: #000; + * } + * ``` + * + * The internal backdrop inherits the value from its host, and its stylesheet reads it: + * + * ```html + * + * #shadow-root + * + * + * ``` + * + * ```css + * // backdrop.scss, compiled + * :host { + * background-color: var(--ion-backdrop-background); + * } + * ``` + * + * A host can sit in another host's shadow root: `ion-datetime` renders an + * `ion-popover`, which renders an `ion-backdrop`. The outer host joins the + * selectors too, so the tokens pass down through both shadow roots. + * + * Add a component here when it starts rendering a migrated component, or a + * component listed here, in its shadow root. Otherwise that child renders with + * no tokens. A spec test checks this. + */ +export const INTERNAL_CHILD_COMPONENTS: Record = { + IonDatetime: ['IonPopover'], + IonMenu: ['IonBackdrop'], + IonModal: ['IonBackdrop'], + IonPopover: ['IonBackdrop'], +}; + +/** + * Returns the tags of the components that render `component` in their shadow root, + * directly or inside another host's shadow root. + */ +const getInternalRenderHosts = (component: string): string[] => { + const hosts = Object.entries(INTERNAL_CHILD_COMPONENTS) + .filter(([, children]) => children.includes(component)) + .map(([host]) => host); + + return hosts.flatMap((host) => [convertToKebabCase(host), ...getInternalRenderHosts(host)]); +}; + +/** + * Generates one block of `---` custom properties for a list of selectors. + */ +const generateBlock = (selectors: string[], tokens: Record, tag: string): string => ` + ${selectors.join(',\n ')} { + ${generateCSSVars(tokens, `--${tag}-`)} + } + `; + /** * Applies a component theme to an element if it exists in the custom theme * @param element The element to apply the theme to diff --git a/docs/sass-guidelines.md b/docs/sass-guidelines.md index 255ba82193e..f723adc22db 100644 --- a/docs/sass-guidelines.md +++ b/docs/sass-guidelines.md @@ -199,7 +199,6 @@ $text-color-rgb: var(--ion-text-color-rgb, $text-color-rgb-value); ```scss // ionic.theme.default.ios.scss -$backdrop-ios-color: var(--ion-backdrop-color, #000); $overlay-ios-background-color: var( --ion-overlay-background-color, var(--ion-background-color-step-100, #f9f9f9) diff --git a/packages/angular/src/lazy/directives/proxies.ts b/packages/angular/src/lazy/directives/proxies.ts index 89bfa1a9f0c..cfc5f27636c 100644 --- a/packages/angular/src/lazy/directives/proxies.ts +++ b/packages/angular/src/lazy/directives/proxies.ts @@ -260,14 +260,14 @@ export declare interface IonAvatar extends Components.IonAvatar {} @ProxyCmp({ - inputs: ['mode', 'stopPropagation', 'tappable', 'theme', 'visible'] + inputs: ['mode', 'stopPropagation', 'tappable', 'visible'] }) @Component({ selector: 'ion-backdrop', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['mode', { name: 'stopPropagation', transform: nullableBooleanAttribute }, { name: 'tappable', transform: nullableBooleanAttribute }, 'theme', { name: 'visible', transform: nullableBooleanAttribute }], + inputs: ['mode', { name: 'stopPropagation', transform: nullableBooleanAttribute }, { name: 'tappable', transform: nullableBooleanAttribute }, { name: 'visible', transform: nullableBooleanAttribute }], outputs: ['ionBackdropTap'], standalone: false }) diff --git a/packages/angular/src/standalone/directives/ion-backdrop.ts b/packages/angular/src/standalone/directives/ion-backdrop.ts index a1a21f3dee1..807eb105551 100644 --- a/packages/angular/src/standalone/directives/ion-backdrop.ts +++ b/packages/angular/src/standalone/directives/ion-backdrop.ts @@ -11,14 +11,14 @@ import { defineCustomElement as defineIonBackdrop } from '@ionic/core/components @ProxyCmp({ defineCustomElementFn: defineIonBackdrop, - inputs: ['mode', 'stopPropagation', 'tappable', 'theme', 'visible'] + inputs: ['mode', 'stopPropagation', 'tappable', 'visible'] }) @Component({ selector: 'ion-backdrop', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['mode', { name: 'stopPropagation', transform: nullableBooleanAttribute }, { name: 'tappable', transform: nullableBooleanAttribute }, 'theme', { name: 'visible', transform: nullableBooleanAttribute }], + inputs: ['mode', { name: 'stopPropagation', transform: nullableBooleanAttribute }, { name: 'tappable', transform: nullableBooleanAttribute }, { name: 'visible', transform: nullableBooleanAttribute }], outputs: ['ionBackdropTap'], }) export class IonBackdrop { From 2229ceff4d888eb16d59be536133992e8235065e Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 2 Nov 2026 15:32:52 -0800 Subject: [PATCH 2/3] chore(backdrop): remove the per-theme stylesheets --- core/src/components/backdrop/backdrop.ionic.scss | 8 -------- core/src/components/backdrop/backdrop.ios.scss | 6 ------ core/src/components/backdrop/backdrop.md.scss | 6 ------ core/src/components/backdrop/backdrop.native.scss | 9 --------- core/src/themes/native/native.globals.scss | 1 - core/src/themes/native/native.theme.default.ios.scss | 1 - core/src/themes/native/native.theme.default.md.scss | 1 - 7 files changed, 32 deletions(-) delete mode 100644 core/src/components/backdrop/backdrop.ionic.scss delete mode 100644 core/src/components/backdrop/backdrop.ios.scss delete mode 100644 core/src/components/backdrop/backdrop.md.scss delete mode 100644 core/src/components/backdrop/backdrop.native.scss diff --git a/core/src/components/backdrop/backdrop.ionic.scss b/core/src/components/backdrop/backdrop.ionic.scss deleted file mode 100644 index 4b53b3916e2..00000000000 --- a/core/src/components/backdrop/backdrop.ionic.scss +++ /dev/null @@ -1,8 +0,0 @@ -@use "../../themes/ionic/ionic.globals.scss" as globals; -@use "./backdrop.common"; - -:host { - background-color: rgba(globals.$ion-primitives-base-black-rgb, 0.7); - - z-index: 2; -} diff --git a/core/src/components/backdrop/backdrop.ios.scss b/core/src/components/backdrop/backdrop.ios.scss deleted file mode 100644 index 0db8fd0f55e..00000000000 --- a/core/src/components/backdrop/backdrop.ios.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "./backdrop.native"; -@import "../../themes/native/native.globals.ios"; - -:host { - background-color: $backdrop-ios-color; -} diff --git a/core/src/components/backdrop/backdrop.md.scss b/core/src/components/backdrop/backdrop.md.scss deleted file mode 100644 index 98c79af2191..00000000000 --- a/core/src/components/backdrop/backdrop.md.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "./backdrop.native"; -@import "../../themes/native/native.globals.md.scss"; - -:host { - background-color: $backdrop-md-color; -} diff --git a/core/src/components/backdrop/backdrop.native.scss b/core/src/components/backdrop/backdrop.native.scss deleted file mode 100644 index d53bb62b8c1..00000000000 --- a/core/src/components/backdrop/backdrop.native.scss +++ /dev/null @@ -1,9 +0,0 @@ -@import "./backdrop.common"; -@import "../../themes/native/native.globals"; - -// Backdrop -// -------------------------------------------------- - -:host { - z-index: $z-index-backdrop; -} diff --git a/core/src/themes/native/native.globals.scss b/core/src/themes/native/native.globals.scss index da620679256..28577912efb 100644 --- a/core/src/themes/native/native.globals.scss +++ b/core/src/themes/native/native.globals.scss @@ -68,7 +68,6 @@ $z-index-toolbar: 10; $z-index-toolbar-background: -1; $z-index-toolbar-buttons: 99; -$z-index-backdrop: 2; $z-index-overlay-wrapper: 10; $z-index-item-options: 1; diff --git a/core/src/themes/native/native.theme.default.ios.scss b/core/src/themes/native/native.theme.default.ios.scss index f1624c51895..f1c421e0cab 100644 --- a/core/src/themes/native/native.theme.default.ios.scss +++ b/core/src/themes/native/native.theme.default.ios.scss @@ -6,7 +6,6 @@ // iOS General Colors // -------------------------------------------------- -$backdrop-ios-color: var(--ion-backdrop-color, #000); $overlay-ios-background-color: var(--ion-overlay-background-color, var(--ion-background-color-step-100, #f9f9f9)); // iOS Tabs & Tab bar diff --git a/core/src/themes/native/native.theme.default.md.scss b/core/src/themes/native/native.theme.default.md.scss index d136cea7c90..4c600314564 100644 --- a/core/src/themes/native/native.theme.default.md.scss +++ b/core/src/themes/native/native.theme.default.md.scss @@ -6,7 +6,6 @@ // Material Design General Colors // -------------------------------------------------- -$backdrop-md-color: var(--ion-backdrop-color, #000); $border-md-color: var(--ion-border-color, var(--ion-background-color-step-150, #c1c4cd)); $overlay-md-background-color: var(--ion-overlay-background-color, var(--ion-background-color, #fff)); From 17bab52b17c7b770f328f7682da6c6cdb619c9ca Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 2 Nov 2026 15:48:46 -0800 Subject: [PATCH 3/3] feat(backdrop): replace the --ion-backdrop-color global variable --- BREAKING.md | 16 ++++++++++++++-- core/src/themes/ios/default.tokens.ts | 2 +- core/src/themes/md/default.tokens.ts | 2 +- .../native/test/css-variables/css/oceanic.css | 7 ++++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/BREAKING.md b/BREAKING.md index db526998fd2..91579f45b0f 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -56,9 +56,21 @@ This is a comprehensive list of the breaking changes introduced in the major ver The following breaking changes apply to `ion-backdrop`: -1. Theme classes (`ion-backdrop.md`, `ion-backdrop.ios`) are no longer supported. +1. The `--ion-backdrop-color` global variable has been replaced. [1](#version-10x-backdrop-replaced-css-variables) +2. Theme classes (`ion-backdrop.md`, `ion-backdrop.ios`) are no longer supported. [2](#version-10x-backdrop-theme-classes) -
Theme classes
+
Replaced CSS variables
+ +Use the new token structure for global styles, or the corresponding CSS variable for component-specific overrides: + +| Old (9.x) | New token (global) | New CSS variable (component-specific) | +|---|---|---| +| `--ion-backdrop-color` | `IonBackdrop.background` | `--ion-backdrop-background` | + +> [!NOTE] +> `--ion-backdrop-color` was set on `:root`, and `--ion-backdrop-background` has no effect there. Set it on the backdrop of each overlay instead: `ion-alert ion-backdrop` for alert, action sheet and loading, and the `backdrop` part for menu, modal and popover (`ion-modal::part(backdrop)`). + +
Theme classes
Remove any instances that target the theme classes: `ion-backdrop.md`, `ion-backdrop.ios`. diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts index 79a695305f1..01f1dbd6e8e 100644 --- a/core/src/themes/ios/default.tokens.ts +++ b/core/src/themes/ios/default.tokens.ts @@ -124,7 +124,7 @@ export const defaultTheme: DefaultTheme = { components: { IonBackdrop: { - background: `var(--ion-backdrop-color, ${baseColors.black})`, + background: baseColors.black, }, IonBadge: { diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts index 01ef34afd12..d7b6d1d87dc 100644 --- a/core/src/themes/md/default.tokens.ts +++ b/core/src/themes/md/default.tokens.ts @@ -118,7 +118,7 @@ export const defaultTheme: DefaultTheme = { components: { IonBackdrop: { - background: `var(--ion-backdrop-color, ${baseColors.black})`, + background: baseColors.black, }, IonBadge: { diff --git a/core/src/themes/native/test/css-variables/css/oceanic.css b/core/src/themes/native/test/css-variables/css/oceanic.css index 046e0265c85..969390dac39 100644 --- a/core/src/themes/native/test/css-variables/css/oceanic.css +++ b/core/src/themes/native/test/css-variables/css/oceanic.css @@ -75,7 +75,6 @@ --ion-color-dark-tint: #343d46; /* Component Colors */ - --ion-backdrop-color: #1b2b34; --ion-overlay-background-color: #142129; --ion-border-color: #1b2b34; @@ -138,3 +137,9 @@ --ion-text-color-step-950: #324048; --ion-text-color-step-1000: #26363e; } + +ion-action-sheet ion-backdrop, +ion-alert ion-backdrop, +ion-loading ion-backdrop { + --ion-backdrop-background: #1b2b34; +}