From 06f42b01b0a406eb070a40af0eaf7024ca36b81a Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Tue, 23 Sep 2025 09:59:52 -0400
Subject: [PATCH 01/22] test(utils): move spec tests into test
---
core/src/utils/{ => test}/helpers.spec.ts | 2 +-
core/src/utils/test/theme.spec.ts | 474 +++++++++++++++++++++-
core/src/utils/theme.spec.ts | 466 ---------------------
3 files changed, 474 insertions(+), 468 deletions(-)
rename core/src/utils/{ => test}/helpers.spec.ts (96%)
delete mode 100644 core/src/utils/theme.spec.ts
diff --git a/core/src/utils/helpers.spec.ts b/core/src/utils/test/helpers.spec.ts
similarity index 96%
rename from core/src/utils/helpers.spec.ts
rename to core/src/utils/test/helpers.spec.ts
index 40b4f44a9c8..42dcb616b11 100644
--- a/core/src/utils/helpers.spec.ts
+++ b/core/src/utils/test/helpers.spec.ts
@@ -1,4 +1,4 @@
-import { deepMerge, inheritAriaAttributes } from './helpers';
+import { deepMerge, inheritAriaAttributes } from '../helpers';
describe('inheritAriaAttributes', () => {
it('should inherit aria attributes', () => {
diff --git a/core/src/utils/test/theme.spec.ts b/core/src/utils/test/theme.spec.ts
index 8b28eba220a..f59c8641b6d 100644
--- a/core/src/utils/test/theme.spec.ts
+++ b/core/src/utils/test/theme.spec.ts
@@ -1,4 +1,16 @@
-import { getClassList, getClassMap } from '../theme';
+import { newSpecPage } from '@stencil/core/testing';
+
+import { CardContent } from '../../components/card-content/card-content';
+import { Chip } from '../../components/chip/chip';
+import {
+ generateComponentThemeCSS,
+ generateCSSVars,
+ generateGlobalThemeCSS,
+ getClassList,
+ getClassMap,
+ getCustomTheme,
+ injectCSS,
+} from '../theme';
describe('getClassList()', () => {
it('should parse string', () => {
@@ -62,3 +74,463 @@ describe('getClassMap()', () => {
});
});
});
+
+describe('getCustomTheme', () => {
+ const baseCustomTheme = {
+ radii: {
+ sm: '14px',
+ md: '18px',
+ lg: '22px',
+ },
+ components: {
+ IonChip: {
+ hue: {
+ subtle: {
+ bg: 'red',
+ color: 'white',
+ },
+ },
+ },
+ },
+ };
+
+ const iosOverride = {
+ components: {
+ IonChip: {
+ hue: {
+ subtle: {
+ bg: 'blue',
+ },
+ },
+ },
+ },
+ };
+
+ const mdOverride = {
+ components: {
+ IonChip: {
+ hue: {
+ subtle: {
+ bg: 'green',
+ },
+ },
+ },
+ },
+ };
+
+ it('should return the custom theme if no mode overrides exist', () => {
+ const customTheme = { ...baseCustomTheme };
+
+ const result = getCustomTheme(customTheme, 'ios');
+
+ expect(result).toEqual(customTheme);
+ });
+
+ it('should combine only with ios overrides if mode is ios', () => {
+ const customTheme = {
+ ...baseCustomTheme,
+ ios: iosOverride,
+ md: mdOverride,
+ };
+
+ const result = getCustomTheme(customTheme, 'ios');
+
+ const expected = {
+ ...baseCustomTheme,
+ components: {
+ IonChip: {
+ hue: {
+ subtle: {
+ bg: 'blue',
+ color: 'white',
+ },
+ },
+ },
+ },
+ };
+
+ expect(result).toEqual(expected);
+ });
+
+ it('should combine only with md overrides if mode is md', () => {
+ const customTheme = {
+ ...baseCustomTheme,
+ ios: iosOverride,
+ md: mdOverride,
+ };
+
+ const result = getCustomTheme(customTheme, 'md');
+
+ const expected = {
+ ...baseCustomTheme,
+ components: {
+ IonChip: {
+ hue: {
+ subtle: {
+ bg: 'green',
+ color: 'white',
+ },
+ },
+ },
+ },
+ };
+
+ expect(result).toEqual(expected);
+ });
+});
+
+describe('generateCSSVars', () => {
+ it('should not generate CSS variables for an empty theme', () => {
+ const theme = {
+ palette: {
+ light: {},
+ dark: {},
+ },
+ };
+
+ const css = generateCSSVars(theme);
+ expect(css).toBe('');
+ });
+
+ it('should generate CSS variables for a given theme', () => {
+ const theme = {
+ palette: {
+ light: {},
+ dark: {
+ enabled: 'system',
+ },
+ },
+ borderWidth: {
+ sm: '4px',
+ },
+ spacing: {
+ md: '12px',
+ },
+ scaling: {
+ 0: '0',
+ },
+ radii: {
+ lg: '8px',
+ },
+ dynamicFont: '-apple-system-body',
+ fontFamily: 'Roboto, "Helvetica Neue", sans-serif',
+ fontWeights: {
+ semiBold: '600',
+ },
+ fontSizes: {
+ sm: '14px',
+ md: '16px',
+ },
+ lineHeights: {
+ sm: '1.2',
+ },
+ components: {},
+ };
+
+ const css = generateCSSVars(theme);
+
+ expect(css).toContain('--ion-palette-dark-enabled: system;');
+ expect(css).toContain('--ion-border-width-sm: 4px;');
+ expect(css).toContain('--ion-spacing-md: 12px;');
+ expect(css).toContain('--ion-scaling-0: 0;');
+ expect(css).toContain('--ion-radii-lg: 8px;');
+ expect(css).toContain('--ion-dynamic-font: -apple-system-body;');
+ expect(css).toContain('--ion-font-family: Roboto, "Helvetica Neue", sans-serif;');
+ expect(css).toContain('--ion-font-weights-semi-bold: 600;');
+ expect(css).toContain('--ion-font-sizes-sm: 14px;');
+ expect(css).toContain('--ion-font-sizes-sm-rem: 0.875rem;');
+ expect(css).toContain('--ion-font-sizes-md: 16px;');
+ expect(css).toContain('--ion-font-sizes-md-rem: 1rem;');
+ expect(css).toContain('--ion-line-heights-sm: 1.2;');
+ });
+});
+
+describe('injectCSS', () => {
+ it('should inject CSS into the head', () => {
+ const css = 'body { background-color: red; }';
+ injectCSS(css);
+ expect(document.head.innerHTML).toContain(``);
+ });
+
+ it('should inject CSS into an element', async () => {
+ const page = await newSpecPage({
+ components: [CardContent],
+ html: '',
+ });
+
+ const target = page.body.querySelector('ion-card-content')!;
+
+ const css = ':host { background-color: red; }';
+ injectCSS(css, target);
+
+ expect(target.innerHTML).toContain(``);
+ });
+
+ it('should inject CSS into an element with a shadow root', async () => {
+ const page = await newSpecPage({
+ components: [Chip],
+ html: '',
+ });
+
+ const target = page.body.querySelector('ion-chip')!;
+ const shadowRoot = target.shadowRoot;
+ expect(shadowRoot).toBeTruthy();
+
+ const css = ':host { background-color: red; }';
+ injectCSS(css, shadowRoot!);
+
+ expect(shadowRoot!.innerHTML).toContain(``);
+ });
+});
+
+describe('generateGlobalThemeCSS', () => {
+ it('should generate global CSS for a given theme', () => {
+ const theme = {
+ palette: {
+ light: {},
+ dark: {
+ enabled: 'never',
+ },
+ },
+ borderWidth: {
+ sm: '4px',
+ },
+ spacing: {
+ md: '12px',
+ },
+ dynamicFont: '-apple-system-body',
+ };
+
+ const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
+
+ const expectedCSS = `
+ :root {
+ --ion-border-width-sm: 4px;
+ --ion-spacing-md: 12px;
+ --ion-dynamic-font: -apple-system-body;
+ }
+ `.replace(/\s/g, '');
+
+ expect(css).toBe(expectedCSS);
+ });
+
+ it('should generate global CSS for a given theme with light palette', () => {
+ const theme = {
+ palette: {
+ light: {
+ color: {
+ primary: {
+ bold: {
+ base: '#0054e9',
+ contrast: '#ffffff',
+ shade: '#0041c4',
+ tint: '#0065ff',
+ },
+ subtle: {
+ base: '#0054e9',
+ contrast: '#ffffff',
+ shade: '#0041c4',
+ tint: '#0065ff',
+ },
+ },
+ red: {
+ 50: '#ffebee',
+ 100: '#ffcdd2',
+ 200: '#ef9a9a',
+ },
+ },
+ },
+ dark: {
+ enabled: 'never',
+ },
+ },
+ borderWidth: {
+ sm: '4px',
+ },
+ spacing: {
+ md: '12px',
+ },
+ dynamicFont: '-apple-system-body',
+ };
+
+ const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
+
+ const expectedCSS = `
+ :root {
+ --ion-border-width-sm: 4px;
+ --ion-spacing-md: 12px;
+ --ion-dynamic-font: -apple-system-body;
+
+ --ion-color-primary-bold: #0054e9;
+ --ion-color-primary-bold-contrast: #ffffff;
+ --ion-color-primary-bold-shade: #0041c4;
+ --ion-color-primary-bold-tint: #0065ff;
+ --ion-color-primary-subtle: #0054e9;
+ --ion-color-primary-subtle-contrast: #ffffff;
+ --ion-color-primary-subtle-shade: #0041c4;
+ --ion-color-primary-subtle-tint: #0065ff;
+ --ion-color-red-50: #ffebee;
+ --ion-color-red-100: #ffcdd2;
+ --ion-color-red-200: #ef9a9a;
+ }
+ `.replace(/\s/g, '');
+
+ expect(css).toBe(expectedCSS);
+ });
+
+ it('should not include component or palette variables in global CSS', () => {
+ const theme = {
+ palette: {
+ light: {},
+ dark: {
+ enabled: 'never',
+ },
+ },
+ borderWidth: {
+ sm: '4px',
+ },
+ spacing: {
+ md: '12px',
+ },
+ components: {
+ IonChip: {
+ hue: {
+ subtle: {
+ bg: 'red',
+ },
+ },
+ shape: {
+ round: {
+ borderRadius: '4px',
+ },
+ },
+ },
+ IonButton: {
+ color: {
+ primary: {
+ bg: 'blue',
+ },
+ },
+ },
+ },
+ };
+
+ const css = generateGlobalThemeCSS(theme);
+
+ // Should include global design tokens
+ expect(css).toContain('--ion-border-width-sm: 4px');
+ expect(css).toContain('--ion-spacing-md: 12px');
+
+ // Should NOT include component variables
+ expect(css).not.toContain('--ion-components-ion-chip-hue-subtle-bg');
+ expect(css).not.toContain('--ion-components-ion-chip-shape-round-border-radius');
+ expect(css).not.toContain('--ion-components-ion-button-color-primary-bg');
+ expect(css).not.toContain('components');
+
+ // Should NOT include palette variables
+ expect(css).not.toContain('--ion-color-palette-dark-enabled-never');
+ expect(css).not.toContain('palette');
+ });
+
+ it('should generate global CSS for a given theme with dark palette enabled for system preference', () => {
+ const theme = {
+ palette: {
+ light: {},
+ dark: {
+ enabled: 'system',
+ color: {
+ primary: {
+ bold: {
+ base: '#0054e9',
+ contrast: '#ffffff',
+ shade: '#0041c4',
+ tint: '#0065ff',
+ },
+ subtle: {
+ base: '#0054e9',
+ contrast: '#ffffff',
+ shade: '#0041c4',
+ tint: '#0065ff',
+ },
+ },
+ red: {
+ 50: '#ffebee',
+ 100: '#ffcdd2',
+ 200: '#ef9a9a',
+ },
+ },
+ },
+ },
+ borderWidth: {
+ sm: '4px',
+ },
+ spacing: {
+ md: '12px',
+ },
+ dynamicFont: '-apple-system-body',
+ };
+
+ const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
+
+ const expectedCSS = `
+ :root {
+ --ion-border-width-sm: 4px;
+ --ion-spacing-md: 12px;
+ --ion-dynamic-font: -apple-system-body;
+ }
+
+ @media(prefers-color-scheme: dark) {
+ :root {
+ --ion-enabled: system;
+ --ion-color-primary-bold: #0054e9;
+ --ion-color-primary-bold-contrast: #ffffff;
+ --ion-color-primary-bold-shade: #0041c4;
+ --ion-color-primary-bold-tint: #0065ff;
+ --ion-color-primary-subtle: #0054e9;
+ --ion-color-primary-subtle-contrast: #ffffff;
+ --ion-color-primary-subtle-shade: #0041c4;
+ --ion-color-primary-subtle-tint: #0065ff;
+ --ion-color-red-50: #ffebee;
+ --ion-color-red-100: #ffcdd2;
+ --ion-color-red-200: #ef9a9a;
+ }
+ }
+ `.replace(/\s/g, '');
+
+ expect(css).toBe(expectedCSS);
+ });
+});
+
+describe('generateComponentThemeCSS', () => {
+ it('should generate component theme CSS for a given theme', () => {
+ const IonChip = {
+ hue: {
+ subtle: {
+ bg: 'red',
+ color: 'white',
+ borderColor: 'black',
+ },
+ bold: {
+ bg: 'blue',
+ color: 'white',
+ borderColor: 'black',
+ },
+ },
+ };
+
+ const css = generateComponentThemeCSS(IonChip, 'chip').replace(/\s/g, '');
+
+ const expectedCSS = `
+ :host(.chip-themed) {
+ --ion-chip-hue-subtle-bg: red;
+ --ion-chip-hue-subtle-color: white;
+ --ion-chip-hue-subtle-border-color: black;
+ --ion-chip-hue-bold-bg: blue;
+ --ion-chip-hue-bold-color: white;
+ --ion-chip-hue-bold-border-color: black;
+ }
+ `.replace(/\s/g, '');
+
+ expect(css).toBe(expectedCSS);
+ });
+});
diff --git a/core/src/utils/theme.spec.ts b/core/src/utils/theme.spec.ts
deleted file mode 100644
index 9a5fb6d38f3..00000000000
--- a/core/src/utils/theme.spec.ts
+++ /dev/null
@@ -1,466 +0,0 @@
-import { newSpecPage } from '@stencil/core/testing';
-
-import { CardContent } from '../components/card-content/card-content';
-import { Chip } from '../components/chip/chip';
-
-import { generateComponentThemeCSS, generateCSSVars, generateGlobalThemeCSS, getCustomTheme, injectCSS } from './theme';
-
-describe('getCustomTheme', () => {
- const baseCustomTheme = {
- radii: {
- sm: '14px',
- md: '18px',
- lg: '22px',
- },
- components: {
- IonChip: {
- hue: {
- subtle: {
- bg: 'red',
- color: 'white',
- },
- },
- },
- },
- };
-
- const iosOverride = {
- components: {
- IonChip: {
- hue: {
- subtle: {
- bg: 'blue',
- },
- },
- },
- },
- };
-
- const mdOverride = {
- components: {
- IonChip: {
- hue: {
- subtle: {
- bg: 'green',
- },
- },
- },
- },
- };
-
- it('should return the custom theme if no mode overrides exist', () => {
- const customTheme = { ...baseCustomTheme };
-
- const result = getCustomTheme(customTheme, 'ios');
-
- expect(result).toEqual(customTheme);
- });
-
- it('should combine only with ios overrides if mode is ios', () => {
- const customTheme = {
- ...baseCustomTheme,
- ios: iosOverride,
- md: mdOverride,
- };
-
- const result = getCustomTheme(customTheme, 'ios');
-
- const expected = {
- ...baseCustomTheme,
- components: {
- IonChip: {
- hue: {
- subtle: {
- bg: 'blue',
- color: 'white',
- },
- },
- },
- },
- };
-
- expect(result).toEqual(expected);
- });
-
- it('should combine only with md overrides if mode is md', () => {
- const customTheme = {
- ...baseCustomTheme,
- ios: iosOverride,
- md: mdOverride,
- };
-
- const result = getCustomTheme(customTheme, 'md');
-
- const expected = {
- ...baseCustomTheme,
- components: {
- IonChip: {
- hue: {
- subtle: {
- bg: 'green',
- color: 'white',
- },
- },
- },
- },
- };
-
- expect(result).toEqual(expected);
- });
-});
-
-describe('generateCSSVars', () => {
- it('should not generate CSS variables for an empty theme', () => {
- const theme = {
- palette: {
- light: {},
- dark: {},
- },
- };
-
- const css = generateCSSVars(theme);
- expect(css).toBe('');
- });
-
- it('should generate CSS variables for a given theme', () => {
- const theme = {
- palette: {
- light: {},
- dark: {
- enabled: 'system',
- },
- },
- borderWidth: {
- sm: '4px',
- },
- spacing: {
- md: '12px',
- },
- scaling: {
- 0: '0',
- },
- radii: {
- lg: '8px',
- },
- dynamicFont: '-apple-system-body',
- fontFamily: 'Roboto, "Helvetica Neue", sans-serif',
- fontWeights: {
- semiBold: '600',
- },
- fontSizes: {
- sm: '14px',
- md: '16px',
- },
- lineHeights: {
- sm: '1.2',
- },
- components: {},
- };
-
- const css = generateCSSVars(theme);
-
- expect(css).toContain('--ion-palette-dark-enabled: system;');
- expect(css).toContain('--ion-border-width-sm: 4px;');
- expect(css).toContain('--ion-spacing-md: 12px;');
- expect(css).toContain('--ion-scaling-0: 0;');
- expect(css).toContain('--ion-radii-lg: 8px;');
- expect(css).toContain('--ion-dynamic-font: -apple-system-body;');
- expect(css).toContain('--ion-font-family: Roboto, "Helvetica Neue", sans-serif;');
- expect(css).toContain('--ion-font-weights-semi-bold: 600;');
- expect(css).toContain('--ion-font-sizes-sm: 14px;');
- expect(css).toContain('--ion-font-sizes-sm-rem: 0.875rem;');
- expect(css).toContain('--ion-font-sizes-md: 16px;');
- expect(css).toContain('--ion-font-sizes-md-rem: 1rem;');
- expect(css).toContain('--ion-line-heights-sm: 1.2;');
- });
-});
-
-describe('injectCSS', () => {
- it('should inject CSS into the head', () => {
- const css = 'body { background-color: red; }';
- injectCSS(css);
- expect(document.head.innerHTML).toContain(``);
- });
-
- it('should inject CSS into an element', async () => {
- const page = await newSpecPage({
- components: [CardContent],
- html: '',
- });
-
- const target = page.body.querySelector('ion-card-content')!;
-
- const css = ':host { background-color: red; }';
- injectCSS(css, target);
-
- expect(target.innerHTML).toContain(``);
- });
-
- it('should inject CSS into an element with a shadow root', async () => {
- const page = await newSpecPage({
- components: [Chip],
- html: '',
- });
-
- const target = page.body.querySelector('ion-chip')!;
- const shadowRoot = target.shadowRoot;
- expect(shadowRoot).toBeTruthy();
-
- const css = ':host { background-color: red; }';
- injectCSS(css, shadowRoot!);
-
- expect(shadowRoot!.innerHTML).toContain(``);
- });
-});
-
-describe('generateGlobalThemeCSS', () => {
- it('should generate global CSS for a given theme', () => {
- const theme = {
- palette: {
- light: {},
- dark: {
- enabled: 'never',
- },
- },
- borderWidth: {
- sm: '4px',
- },
- spacing: {
- md: '12px',
- },
- dynamicFont: '-apple-system-body',
- };
-
- const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
-
- const expectedCSS = `
- :root {
- --ion-border-width-sm: 4px;
- --ion-spacing-md: 12px;
- --ion-dynamic-font: -apple-system-body;
- }
- `.replace(/\s/g, '');
-
- expect(css).toBe(expectedCSS);
- });
-
- it('should generate global CSS for a given theme with light palette', () => {
- const theme = {
- palette: {
- light: {
- color: {
- primary: {
- bold: {
- base: '#0054e9',
- contrast: '#ffffff',
- shade: '#0041c4',
- tint: '#0065ff',
- },
- subtle: {
- base: '#0054e9',
- contrast: '#ffffff',
- shade: '#0041c4',
- tint: '#0065ff',
- },
- },
- red: {
- 50: '#ffebee',
- 100: '#ffcdd2',
- 200: '#ef9a9a',
- },
- },
- },
- dark: {
- enabled: 'never',
- },
- },
- borderWidth: {
- sm: '4px',
- },
- spacing: {
- md: '12px',
- },
- dynamicFont: '-apple-system-body',
- };
-
- const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
-
- const expectedCSS = `
- :root {
- --ion-border-width-sm: 4px;
- --ion-spacing-md: 12px;
- --ion-dynamic-font: -apple-system-body;
-
- --ion-color-primary-bold: #0054e9;
- --ion-color-primary-bold-contrast: #ffffff;
- --ion-color-primary-bold-shade: #0041c4;
- --ion-color-primary-bold-tint: #0065ff;
- --ion-color-primary-subtle: #0054e9;
- --ion-color-primary-subtle-contrast: #ffffff;
- --ion-color-primary-subtle-shade: #0041c4;
- --ion-color-primary-subtle-tint: #0065ff;
- --ion-color-red-50: #ffebee;
- --ion-color-red-100: #ffcdd2;
- --ion-color-red-200: #ef9a9a;
- }
- `.replace(/\s/g, '');
-
- expect(css).toBe(expectedCSS);
- });
-
- it('should not include component or palette variables in global CSS', () => {
- const theme = {
- palette: {
- light: {},
- dark: {
- enabled: 'never',
- },
- },
- borderWidth: {
- sm: '4px',
- },
- spacing: {
- md: '12px',
- },
- components: {
- IonChip: {
- hue: {
- subtle: {
- bg: 'red',
- },
- },
- shape: {
- round: {
- borderRadius: '4px',
- },
- },
- },
- IonButton: {
- color: {
- primary: {
- bg: 'blue',
- },
- },
- },
- },
- };
-
- const css = generateGlobalThemeCSS(theme);
-
- // Should include global design tokens
- expect(css).toContain('--ion-border-width-sm: 4px');
- expect(css).toContain('--ion-spacing-md: 12px');
-
- // Should NOT include component variables
- expect(css).not.toContain('--ion-components-ion-chip-hue-subtle-bg');
- expect(css).not.toContain('--ion-components-ion-chip-shape-round-border-radius');
- expect(css).not.toContain('--ion-components-ion-button-color-primary-bg');
- expect(css).not.toContain('components');
-
- // Should NOT include palette variables
- expect(css).not.toContain('--ion-color-palette-dark-enabled-never');
- expect(css).not.toContain('palette');
- });
-
- it('should generate global CSS for a given theme with dark palette enabled for system preference', () => {
- const theme = {
- palette: {
- light: {},
- dark: {
- enabled: 'system',
- color: {
- primary: {
- bold: {
- base: '#0054e9',
- contrast: '#ffffff',
- shade: '#0041c4',
- tint: '#0065ff',
- },
- subtle: {
- base: '#0054e9',
- contrast: '#ffffff',
- shade: '#0041c4',
- tint: '#0065ff',
- },
- },
- red: {
- 50: '#ffebee',
- 100: '#ffcdd2',
- 200: '#ef9a9a',
- },
- },
- },
- },
- borderWidth: {
- sm: '4px',
- },
- spacing: {
- md: '12px',
- },
- dynamicFont: '-apple-system-body',
- };
-
- const css = generateGlobalThemeCSS(theme).replace(/\s/g, '');
-
- const expectedCSS = `
- :root {
- --ion-border-width-sm: 4px;
- --ion-spacing-md: 12px;
- --ion-dynamic-font: -apple-system-body;
- }
-
- @media(prefers-color-scheme: dark) {
- :root {
- --ion-enabled: system;
- --ion-color-primary-bold: #0054e9;
- --ion-color-primary-bold-contrast: #ffffff;
- --ion-color-primary-bold-shade: #0041c4;
- --ion-color-primary-bold-tint: #0065ff;
- --ion-color-primary-subtle: #0054e9;
- --ion-color-primary-subtle-contrast: #ffffff;
- --ion-color-primary-subtle-shade: #0041c4;
- --ion-color-primary-subtle-tint: #0065ff;
- --ion-color-red-50: #ffebee;
- --ion-color-red-100: #ffcdd2;
- --ion-color-red-200: #ef9a9a;
- }
- }
- `.replace(/\s/g, '');
-
- expect(css).toBe(expectedCSS);
- });
-});
-
-describe('generateComponentThemeCSS', () => {
- it('should generate component theme CSS for a given theme', () => {
- const IonChip = {
- hue: {
- subtle: {
- bg: 'red',
- color: 'white',
- borderColor: 'black',
- },
- bold: {
- bg: 'blue',
- color: 'white',
- borderColor: 'black',
- },
- },
- };
-
- const css = generateComponentThemeCSS(IonChip, 'chip').replace(/\s/g, '');
-
- const expectedCSS = `
- :host(.chip-themed) {
- --ion-chip-hue-subtle-bg: red;
- --ion-chip-hue-subtle-color: white;
- --ion-chip-hue-subtle-border-color: black;
- --ion-chip-hue-bold-bg: blue;
- --ion-chip-hue-bold-color: white;
- --ion-chip-hue-bold-border-color: black;
- }
- `.replace(/\s/g, '');
-
- expect(css).toBe(expectedCSS);
- });
-});
From f565952b829a2ac00870a8db0786f6d3984158f8 Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Tue, 23 Sep 2025 16:02:40 -0400
Subject: [PATCH 02/22] test(themes): add basic, color and typography tests
---
core/src/themes/test/basic/index.html | 288 +++++++++++++++++++++
core/src/themes/test/color/index.html | 239 +++++++++++++++++
core/src/themes/test/typography/index.html | 191 ++++++++++++++
3 files changed, 718 insertions(+)
create mode 100644 core/src/themes/test/basic/index.html
create mode 100644 core/src/themes/test/color/index.html
create mode 100644 core/src/themes/test/typography/index.html
diff --git a/core/src/themes/test/basic/index.html b/core/src/themes/test/basic/index.html
new file mode 100644
index 00000000000..ec328d34dd7
--- /dev/null
+++ b/core/src/themes/test/basic/index.html
@@ -0,0 +1,288 @@
+
+
+
+
+ Themes - Basic
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Themes - Basic
+
+
+
+
+
+
+
Scaling
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Spacing
+
+
+
+
+
+
+
+
+
+
+
Radii
+
none
+
xxs
+
xs
+
sm
+
md
+
lg
+
xl
+
xxl
+
+
+
Border Width
+
none
+
xxs
+
xs
+
sm
+
md
+
lg
+
xl
+
xxl
+
+
+
+
+
+
diff --git a/core/src/themes/test/color/index.html b/core/src/themes/test/color/index.html
new file mode 100644
index 00000000000..fb186978291
--- /dev/null
+++ b/core/src/themes/test/color/index.html
@@ -0,0 +1,239 @@
+
+
+
+
+ Themes - Color
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Themes - Color
+
+
+
+
+
+
+
Bold Colors
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+
+
Subtle Colors
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+ Base
+ Shade
+ Tint
+ Foreground
+
+
+
+
+
+
+
diff --git a/core/src/themes/test/typography/index.html b/core/src/themes/test/typography/index.html
new file mode 100644
index 00000000000..8c2b7d3557b
--- /dev/null
+++ b/core/src/themes/test/typography/index.html
@@ -0,0 +1,191 @@
+
+
+
+
+ Themes - Typography
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Themes - Typography
+
+
+
+
+
+
+
Font Size
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+
Font Weight
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+
+
Line Height
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.
+
+
+
+
+
+
From 58b006d61cb41c7a10cb8dce128f229466de7a18 Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Tue, 23 Sep 2025 16:03:15 -0400
Subject: [PATCH 03/22] feat(themes): define base tokens, color functions and
interfaces
---
core/src/global/ionic-global.ts | 2 +-
core/src/themes/base/dark.tokens.ts | 164 ++++++++++++++++++++++
core/src/themes/base/default.tokens.ts | 105 +++++++++++++-
core/src/themes/base/light.tokens.ts | 163 +++++++++++++++++++++
core/src/themes/themes.interfaces.ts | 145 +++++++++++++++++++
core/src/utils/theme.ts | 187 ++++++++++++++++++++++++-
6 files changed, 757 insertions(+), 9 deletions(-)
create mode 100644 core/src/themes/themes.interfaces.ts
diff --git a/core/src/global/ionic-global.ts b/core/src/global/ionic-global.ts
index 95e788aeb58..827c1810847 100644
--- a/core/src/global/ionic-global.ts
+++ b/core/src/global/ionic-global.ts
@@ -4,7 +4,7 @@ import { applyGlobalTheme, getCustomTheme } from '@utils/theme';
import type { IonicConfig, Mode, Theme } from '../interface';
import { defaultTheme as baseTheme } from '../themes/base/default.tokens';
-import type { Theme as BaseTheme } from '../themes/base/default.tokens';
+import type { BaseTheme } from '../themes/themes.interfaces';
import { shouldUseCloseWatcher } from '../utils/hardware-back-button';
import { isPlatform, setupPlatforms } from '../utils/platform';
diff --git a/core/src/themes/base/dark.tokens.ts b/core/src/themes/base/dark.tokens.ts
index e69de29bb2d..529fbed397d 100644
--- a/core/src/themes/base/dark.tokens.ts
+++ b/core/src/themes/base/dark.tokens.ts
@@ -0,0 +1,164 @@
+import { mix } from '../../utils/theme';
+import type { DarkTheme } from '../themes.interfaces';
+
+const colors = {
+ primary: '#4d8dff',
+ secondary: '#46b1ff',
+ tertiary: '#8482fb',
+ success: '#2dd55b',
+ warning: '#ffce31',
+ danger: '#f24c58',
+ light: '#222428',
+ medium: '#989aa2',
+ dark: '#f4f5f8',
+};
+
+export const darkTheme: DarkTheme = {
+ enabled: 'never',
+ color: {
+ primary: {
+ bold: {
+ base: colors.primary,
+ contrast: '#fff',
+ foreground: mix(colors.primary, '#000', '4%'),
+ shade: mix(colors.primary, '#000', '4%'),
+ tint: mix(colors.primary, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.primary, '8%'),
+ contrast: colors.primary,
+ foreground: mix(colors.primary, '#000', '4%'),
+ shade: mix('#000', colors.primary, '4%'),
+ tint: mix('#000', colors.primary, '12%'),
+ },
+ },
+ secondary: {
+ bold: {
+ base: colors.secondary,
+ contrast: '#fff',
+ foreground: mix(colors.secondary, '#000', '4%'),
+ shade: mix(colors.secondary, '#000', '4%'),
+ tint: mix(colors.secondary, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.secondary, '8%'),
+ contrast: colors.secondary,
+ foreground: mix(colors.secondary, '#000', '4%'),
+ shade: mix('#000', colors.secondary, '4%'),
+ tint: mix('#000', colors.secondary, '12%'),
+ },
+ },
+ tertiary: {
+ bold: {
+ base: colors.tertiary,
+ contrast: '#fff',
+ foreground: mix(colors.tertiary, '#000', '4%'),
+ shade: mix(colors.tertiary, '#000', '4%'),
+ tint: mix(colors.tertiary, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.tertiary, '8%'),
+ contrast: colors.tertiary,
+ foreground: mix(colors.tertiary, '#000', '4%'),
+ shade: mix('#000', colors.tertiary, '4%'),
+ tint: mix('#000', colors.tertiary, '12%'),
+ },
+ },
+ success: {
+ bold: {
+ base: colors.success,
+ contrast: '#fff',
+ foreground: mix(colors.success, '#000', '4%'),
+ shade: mix(colors.success, '#000', '4%'),
+ tint: mix(colors.success, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.success, '8%'),
+ contrast: colors.success,
+ foreground: mix(colors.success, '#000', '4%'),
+ shade: mix('#000', colors.success, '4%'),
+ tint: mix('#000', colors.success, '12%'),
+ },
+ },
+ warning: {
+ bold: {
+ base: colors.warning,
+ contrast: '#fff',
+ foreground: mix(colors.warning, '#000', '4%'),
+ shade: mix(colors.warning, '#000', '4%'),
+ tint: mix(colors.warning, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.warning, '8%'),
+ contrast: colors.warning,
+ foreground: mix(colors.warning, '#000', '4%'),
+ shade: mix('#000', colors.warning, '4%'),
+ tint: mix('#000', colors.warning, '12%'),
+ },
+ },
+ danger: {
+ bold: {
+ base: colors.danger,
+ contrast: '#fff',
+ foreground: mix(colors.danger, '#000', '4%'),
+ shade: mix(colors.danger, '#000', '4%'),
+ tint: mix(colors.danger, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.danger, '8%'),
+ contrast: colors.danger,
+ foreground: mix(colors.danger, '#000', '4%'),
+ shade: mix('#000', colors.danger, '4%'),
+ tint: mix('#000', colors.danger, '12%'),
+ },
+ },
+ light: {
+ bold: {
+ base: colors.light,
+ contrast: '#000',
+ foreground: mix(colors.light, '#000', '4%'),
+ shade: mix(colors.light, '#000', '4%'),
+ tint: mix(colors.light, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.light, '8%'),
+ contrast: colors.light,
+ foreground: mix(colors.light, '#000', '4%'),
+ shade: mix('#000', colors.light, '4%'),
+ tint: mix('#000', colors.light, '12%'),
+ },
+ },
+ medium: {
+ bold: {
+ base: colors.medium,
+ contrast: '#fff',
+ foreground: mix(colors.medium, '#000', '4%'),
+ shade: mix(colors.medium, '#000', '4%'),
+ tint: mix(colors.medium, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.medium, '8%'),
+ contrast: colors.medium,
+ foreground: mix(colors.medium, '#000', '4%'),
+ shade: mix('#000', colors.medium, '4%'),
+ tint: mix('#000', colors.medium, '12%'),
+ },
+ },
+ dark: {
+ bold: {
+ base: colors.dark,
+ contrast: '#fff',
+ foreground: mix(colors.dark, '#000', '4%'),
+ shade: mix(colors.dark, '#000', '4%'),
+ tint: mix(colors.dark, '#fff', '12%'),
+ },
+ subtle: {
+ base: mix('#000', colors.dark, '8%'),
+ contrast: colors.dark,
+ foreground: mix(colors.dark, '#000', '4%'),
+ shade: mix('#000', colors.dark, '4%'),
+ tint: mix('#000', colors.dark, '12%'),
+ },
+ },
+ },
+};
diff --git a/core/src/themes/base/default.tokens.ts b/core/src/themes/base/default.tokens.ts
index 7278514d484..979be9dcd46 100644
--- a/core/src/themes/base/default.tokens.ts
+++ b/core/src/themes/base/default.tokens.ts
@@ -1,10 +1,101 @@
-export const defaultTheme = {
+import type { DefaultTheme } from '../themes.interfaces';
+
+import { darkTheme } from './dark.tokens';
+import { lightTheme } from './light.tokens';
+
+export const defaultTheme: DefaultTheme = {
palette: {
- light: {},
- dark: {
- enabled: 'system',
- },
+ light: lightTheme,
+ dark: darkTheme,
+ },
+
+ spacing: {
+ none: '0',
+ xxs: '4px',
+ xs: '6px',
+ sm: '8px',
+ md: '12px',
+ lg: '16px',
+ xl: '24px',
+ xxl: '32px',
+ },
+
+ scaling: {
+ 0: '0',
+ 100: '4px',
+ 150: '6px',
+ 200: '8px',
+ 250: '10px',
+ 300: '12px',
+ 350: '14px',
+ 400: '16px',
+ 450: '18px',
+ 500: '20px',
+ 550: '22px',
+ 600: '24px',
+ 650: '26px',
+ 700: '28px',
+ 750: '30px',
+ 800: '32px',
+ 850: '34px',
+ 900: '36px',
+ },
+
+ borderWidth: {
+ none: '0',
+ xxs: '1px',
+ xs: '2px',
+ sm: '4px',
+ md: '6px',
+ lg: '8px',
+ xl: '10px',
+ xxl: '12px',
},
-};
-export type Theme = typeof defaultTheme;
+ radii: {
+ none: '0',
+ xxs: '1px',
+ xs: '2px',
+ sm: '4px',
+ md: '8px',
+ lg: '12px',
+ xl: '16px',
+ xxl: '32px',
+ },
+
+ fontFamily: 'Roboto, "Helvetica Neue", sans-serif',
+ dynamicFont: '-apple-system-body',
+
+ fontSize: {
+ root: '16px',
+ xxs: '10px',
+ xs: '12px',
+ sm: '14px',
+ md: '16px',
+ lg: '18px',
+ xl: '20px',
+ xxl: '24px',
+ },
+
+ fontWeight: {
+ thin: '100',
+ extraLight: '200',
+ light: '300',
+ normal: '400',
+ medium: '500',
+ semiBold: '600',
+ bold: '700',
+ extraBold: '800',
+ black: '900',
+ },
+
+ lineHeight: {
+ xxs: '1',
+ xs: '1.2',
+ sm: '1.4',
+ md: '1.6',
+ lg: '1.8',
+ xl: '2',
+ xxl: '2.4',
+ },
+};
diff --git a/core/src/themes/base/light.tokens.ts b/core/src/themes/base/light.tokens.ts
index e69de29bb2d..c87499e2839 100644
--- a/core/src/themes/base/light.tokens.ts
+++ b/core/src/themes/base/light.tokens.ts
@@ -0,0 +1,163 @@
+import { mix } from '../../utils/theme';
+import type { LightTheme } from '../themes.interfaces';
+
+const colors = {
+ primary: '#0054e9',
+ secondary: '#0163aa',
+ tertiary: '#6030ff',
+ success: '#2dd55b',
+ warning: '#ffc409',
+ danger: '#c5000f',
+ light: '#f4f5f8',
+ medium: '#636469',
+ dark: '#222428',
+};
+
+export const lightTheme: LightTheme = {
+ color: {
+ primary: {
+ bold: {
+ base: colors.primary,
+ contrast: '#fff',
+ foreground: mix(colors.primary, '#000', '12%'),
+ shade: mix(colors.primary, '#000', '12%'),
+ tint: mix(colors.primary, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.primary, '8%'),
+ contrast: colors.primary,
+ foreground: mix(colors.primary, '#000', '12%'),
+ shade: mix('#fff', colors.primary, '12%'),
+ tint: mix('#fff', colors.primary, '4%'),
+ },
+ },
+ secondary: {
+ bold: {
+ base: colors.secondary,
+ contrast: '#fff',
+ foreground: mix(colors.secondary, '#000', '12%'),
+ shade: mix(colors.secondary, '#000', '12%'),
+ tint: mix(colors.secondary, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.secondary, '8%'),
+ contrast: colors.secondary,
+ foreground: mix(colors.secondary, '#000', '12%'),
+ shade: mix('#fff', colors.secondary, '12%'),
+ tint: mix('#fff', colors.secondary, '4%'),
+ },
+ },
+ tertiary: {
+ bold: {
+ base: colors.tertiary,
+ contrast: '#fff',
+ foreground: mix(colors.tertiary, '#000', '12%'),
+ shade: mix(colors.tertiary, '#000', '12%'),
+ tint: mix(colors.tertiary, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.tertiary, '8%'),
+ contrast: colors.tertiary,
+ foreground: mix(colors.tertiary, '#000', '12%'),
+ shade: mix('#fff', colors.tertiary, '12%'),
+ tint: mix('#fff', colors.tertiary, '4%'),
+ },
+ },
+ success: {
+ bold: {
+ base: colors.success,
+ contrast: '#fff',
+ foreground: mix(colors.success, '#000', '12%'),
+ shade: mix(colors.success, '#000', '12%'),
+ tint: mix(colors.success, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.success, '8%'),
+ contrast: colors.success,
+ foreground: mix(colors.success, '#000', '12%'),
+ shade: mix('#fff', colors.success, '12%'),
+ tint: mix('#fff', colors.success, '4%'),
+ },
+ },
+ warning: {
+ bold: {
+ base: colors.warning,
+ contrast: '#fff',
+ foreground: mix(colors.warning, '#000', '12%'),
+ shade: mix(colors.warning, '#000', '12%'),
+ tint: mix(colors.warning, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.warning, '8%'),
+ contrast: colors.warning,
+ foreground: mix(colors.warning, '#000', '12%'),
+ shade: mix('#fff', colors.warning, '12%'),
+ tint: mix('#fff', colors.warning, '4%'),
+ },
+ },
+ danger: {
+ bold: {
+ base: colors.danger,
+ contrast: '#fff',
+ foreground: mix(colors.danger, '#000', '12%'),
+ shade: mix(colors.danger, '#000', '12%'),
+ tint: mix(colors.danger, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.danger, '8%'),
+ contrast: colors.danger,
+ foreground: mix(colors.danger, '#000', '12%'),
+ shade: mix('#fff', colors.danger, '12%'),
+ tint: mix('#fff', colors.danger, '4%'),
+ },
+ },
+ light: {
+ bold: {
+ base: colors.light,
+ contrast: '#000',
+ foreground: mix(colors.light, '#000', '12%'),
+ shade: mix(colors.light, '#000', '12%'),
+ tint: mix(colors.light, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.light, '8%'),
+ contrast: colors.light,
+ foreground: mix(colors.light, '#000', '12%'),
+ shade: mix('#fff', colors.light, '12%'),
+ tint: mix('#fff', colors.light, '4%'),
+ },
+ },
+ medium: {
+ bold: {
+ base: colors.medium,
+ contrast: '#fff',
+ foreground: mix(colors.medium, '#000', '12%'),
+ shade: mix(colors.medium, '#000', '12%'),
+ tint: mix(colors.medium, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.medium, '8%'),
+ contrast: colors.medium,
+ foreground: mix(colors.medium, '#000', '12%'),
+ shade: mix('#fff', colors.medium, '12%'),
+ tint: mix('#fff', colors.medium, '4%'),
+ },
+ },
+ dark: {
+ bold: {
+ base: colors.dark,
+ contrast: '#fff',
+ foreground: mix(colors.dark, '#000', '12%'),
+ shade: mix(colors.dark, '#000', '12%'),
+ tint: mix(colors.dark, '#fff', '10%'),
+ },
+ subtle: {
+ base: mix('#fff', colors.dark, '8%'),
+ contrast: colors.dark,
+ foreground: mix(colors.dark, '#000', '12%'),
+ shade: mix('#fff', colors.dark, '12%'),
+ tint: mix('#fff', colors.dark, '4%'),
+ },
+ },
+ },
+};
diff --git a/core/src/themes/themes.interfaces.ts b/core/src/themes/themes.interfaces.ts
new file mode 100644
index 00000000000..3d5ac09a4e0
--- /dev/null
+++ b/core/src/themes/themes.interfaces.ts
@@ -0,0 +1,145 @@
+// Platform-specific theme
+export type PlatformTheme = Omit;
+
+// Base tokens for all palettes
+export type BaseTheme = {
+ // SPACE TOKENS
+ spacing?: {
+ none?: string;
+ xxs?: string;
+ xs?: string;
+ sm?: string;
+ md?: string;
+ lg?: string;
+ xl?: string;
+ xxl?: string;
+ };
+
+ scaling?: {
+ 0?: string;
+ 100?: string;
+ 150?: string;
+ 200?: string;
+ 250?: string;
+ 300?: string;
+ 350?: string;
+ 400?: string;
+ 450?: string;
+ 500?: string;
+ 550?: string;
+ 600?: string;
+ 650?: string;
+ 700?: string;
+ 750?: string;
+ 800?: string;
+ 850?: string;
+ 900?: string;
+ };
+
+ // APPEARANCE TOKENS
+ borderWidth?: {
+ none?: string;
+ xxs?: string;
+ xs?: string;
+ sm?: string;
+ md?: string;
+ lg?: string;
+ xl?: string;
+ xxl?: string;
+ };
+
+ radii?: {
+ none?: string;
+ xxs?: string;
+ xs?: string;
+ sm?: string;
+ md?: string;
+ lg?: string;
+ xl?: string;
+ xxl?: string;
+ };
+
+ // TYPOGRAPHY TOKENS
+ dynamicFont?: string;
+ fontFamily?: string;
+
+ fontWeight?: {
+ thin?: string;
+ extraLight?: string;
+ light?: string;
+ normal?: string;
+ medium?: string;
+ semiBold?: string;
+ bold?: string;
+ extraBold?: string;
+ black?: string;
+ };
+
+ fontSize?: {
+ root?: string;
+ xxs?: string;
+ xs?: string;
+ sm?: string;
+ md?: string;
+ lg?: string;
+ xl?: string;
+ xxl?: string;
+ };
+
+ lineHeight?: {
+ xxs?: string;
+ xs?: string;
+ sm?: string;
+ md?: string;
+ lg?: string;
+ xl?: string;
+ xxl?: string;
+ };
+
+ // COMPONENT OVERRIDES
+ components?: {
+ [key: string]: {
+ [key: string]: string;
+ };
+ };
+
+ // COLOR TOKENS
+ color?: {
+ [key: string]: {
+ bold: {
+ base: string;
+ contrast: string;
+ foreground: string;
+ shade: string;
+ tint: string;
+ };
+ subtle: {
+ base: string;
+ contrast: string;
+ foreground: string;
+ shade: string;
+ tint: string;
+ };
+ };
+ };
+
+ // PLATFORM SPECIFIC OVERRIDES
+ ios?: PlatformTheme;
+ md?: PlatformTheme;
+};
+
+// Dark theme interface
+export type DarkTheme = BaseTheme & {
+ enabled: 'system' | 'always' | 'never' | 'class';
+};
+
+// Light theme interface
+export type LightTheme = BaseTheme;
+
+// Default theme interface
+export type DefaultTheme = BaseTheme & {
+ palette?: {
+ light?: LightTheme;
+ dark?: DarkTheme;
+ };
+};
diff --git a/core/src/utils/theme.ts b/core/src/utils/theme.ts
index d71fe43a385..6b2d6708f9e 100644
--- a/core/src/utils/theme.ts
+++ b/core/src/utils/theme.ts
@@ -89,6 +89,28 @@ export const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX):
return [`${prefix.slice(0, -1)}: ${val};`];
}
+ // Generate rgb variables for base and contrast color variants
+ if (key === 'bold' || key === 'subtle') {
+ if (typeof val === 'object' && val !== null) {
+ return Object.entries(val).flatMap(([property, hexValue]) => {
+ if (typeof hexValue === 'string' && hexValue.startsWith('#')) {
+ // For 'base' property, don't include the property name in the CSS variable
+ const varName = property === 'base' ? `${prefix}${key}` : `${prefix}${key}-${property}`;
+ const cssVars = [`${varName}: ${hexValue};`];
+
+ // Only add RGB values for base and contrast
+ if (property === 'base' || property === 'contrast') {
+ const rgbVarName = property === 'base' ? `${prefix}${key}-rgb` : `${prefix}${key}-${property}-rgb`;
+ cssVars.push(`${rgbVarName}: ${hexToRgb(hexValue)};`);
+ }
+
+ return cssVars;
+ }
+ return [];
+ });
+ }
+ }
+
// If it's a font-sizes key, create rem version
// This is necessary to support the dynamic font size feature
if (key === 'font-sizes' && typeof val === 'object' && val !== null) {
@@ -114,6 +136,96 @@ export const generateCSSVars = (theme: any, prefix: string = CSS_PROPS_PREFIX):
return cssProps.join('\n');
};
+/**
+ * Generates a CSS class containing the CSS variables for each color
+ * in the theme. Each color has generic bold and subtle variables that are mapped
+ * to the specific color's bold and subtle variables. The bold colors will temporarily
+ * include a fallback to remove the bold prefix. For example, the primary
+ * color will return the following CSS class:
+ *
+ * ```css
+ * :root .ion-color-primary {
+ * --ion-color-base: var(--ion-color-primary, var(--ion-color-primary-bold));
+ * --ion-color-base-rgb: var(--ion-color-primary-rgb, var(--ion-color-primary-bold-rgb));
+ * --ion-color-contrast: var(--ion-color-primary-contrast, var(--ion-color-primary-bold-contrast));
+ * --ion-color-contrast-rgb: var(--ion-color-primary-contrast-rgb, var(--ion-color-primary-bold-contrast-rgb));
+ * --ion-color-shade: var(--ion-color-primary-shade, var(--ion-color-primary-bold-shade));
+ * --ion-color-tint: var(--ion-color-primary-tint, var(--ion-color-primary-bold-tint));
+ * --ion-color-foreground: var(--ion-color-primary, var(--ion-color-primary-bold-foreground));
+ *
+ * --ion-color-subtle-base: var(--ion-color-primary-subtle);
+ * --ion-color-subtle-base-rgb: var(--ion-color-primary-subtle-rgb);
+ * --ion-color-subtle-contrast: var(--ion-color-primary-subtle-contrast);
+ * --ion-color-subtle-contrast-rgb: var(--ion-color-primary-subtle-contrast-rgb);
+ * --ion-color-subtle-shade: var(--ion-color-primary-subtle-shade);
+ * --ion-color-subtle-tint: var(--ion-color-primary-subtle-tint);
+ * --ion-color-subtle-foreground: var(--ion-color-primary-subtle-foreground);
+ * }
+ * ```
+ *
+ * @param theme The theme object containing color definitions
+ * @returns CSS string with .ion-color-{colorName} utility classes
+ */
+export const generateColorClasses = (theme: any): string => {
+ // Look for colors in the light palette first, then fallback to the
+ // direct color property if there is no light palette
+ const colors = theme?.palette?.light?.color || theme?.color;
+
+ if (!colors || typeof colors !== 'object') {
+ return '';
+ }
+
+ const generatedColorClasses: string[] = [];
+
+ Object.keys(colors).forEach((colorName) => {
+ const colorVariants = colors[colorName];
+ if (!colorVariants || typeof colorVariants !== 'object') return;
+
+ const cssVariableRules: string[] = [];
+
+ // Generate CSS variables for bold variant
+ // Includes base color variables without the bold modifier for
+ // backwards compatibility. The foreground variables falls back to the
+ // base color because it is new.
+ if (colorVariants.bold) {
+ cssVariableRules.push(
+ `--ion-color-base: var(--ion-color-${colorName}, var(--ion-color-${colorName}-bold)) !important;`,
+ `--ion-color-base-rgb: var(--ion-color-${colorName}-rgb, var(--ion-color-${colorName}-bold-rgb)) !important;`,
+ `--ion-color-contrast: var(--ion-color-${colorName}-contrast, var(--ion-color-${colorName}-bold-contrast)) !important;`,
+ `--ion-color-contrast-rgb: var(--ion-color-${colorName}-contrast-rgb, var(--ion-color-${colorName}-bold-contrast-rgb)) !important;`,
+ `--ion-color-shade: var(--ion-color-${colorName}-shade, var(--ion-color-${colorName}-bold-shade)) !important;`,
+ `--ion-color-tint: var(--ion-color-${colorName}-tint, var(--ion-color-${colorName}-bold-tint)) !important;`,
+ `--ion-color-foreground: var(--ion-color-${colorName}, var(--ion-color-${colorName}-bold-foreground)) !important;`
+ );
+ }
+
+ // Generate CSS variables for subtle variant
+ if (colorVariants.subtle) {
+ cssVariableRules.push(
+ `--ion-color-subtle-base: var(--ion-color-${colorName}-subtle) !important;`,
+ `--ion-color-subtle-base-rgb: var(--ion-color-${colorName}-subtle-rgb) !important;`,
+ `--ion-color-subtle-contrast: var(--ion-color-${colorName}-subtle-contrast) !important;`,
+ `--ion-color-subtle-contrast-rgb: var(--ion-color-${colorName}-subtle-contrast-rgb) !important;`,
+ `--ion-color-subtle-shade: var(--ion-color-${colorName}-subtle-shade) !important;`,
+ `--ion-color-subtle-tint: var(--ion-color-${colorName}-subtle-tint) !important;`,
+ `--ion-color-subtle-foreground: var(--ion-color-${colorName}-subtle-foreground) !important;`
+ );
+ }
+
+ if (cssVariableRules.length > 0) {
+ const colorUtilityClass = `
+ :root .ion-color-${colorName} {
+ ${cssVariableRules.join('\n ')}
+ }
+ `;
+
+ generatedColorClasses.push(colorUtilityClass);
+ }
+ });
+
+ return generatedColorClasses.join('\n');
+};
+
/**
* Creates a style element and injects its CSS into a target element
* @param css The CSS string to inject
@@ -172,7 +284,10 @@ export const generateGlobalThemeCSS = (theme: any): string => {
}
}
- return css;
+ // Add color classes
+ const colorClasses = generateColorClasses(theme);
+
+ return css + '\n' + colorClasses;
};
/**
@@ -254,3 +369,73 @@ export const applyComponentTheme = (element: HTMLElement): void => {
injectCSS(css, root);
}
};
+
+/**
+ * Converts a hex color to RGB comma-separated values
+ * @param hex Hex color (e.g., '#ffffff' or '#fff')
+ * @returns RGB string (e.g., '255, 255, 255')
+ */
+export const hexToRgb = (hex: string): string => {
+ const cleanHex = hex.replace('#', '');
+
+ let r: number, g: number, b: number;
+
+ if (cleanHex.length === 3) {
+ // Short hex format like 'fff' → expand to 'ffffff'
+ r = parseInt(cleanHex[0] + cleanHex[0], 16);
+ g = parseInt(cleanHex[1] + cleanHex[1], 16);
+ b = parseInt(cleanHex[2] + cleanHex[2], 16);
+ } else {
+ // Full hex format like 'ffffff'
+ r = parseInt(cleanHex.substr(0, 2), 16);
+ g = parseInt(cleanHex.substr(2, 2), 16);
+ b = parseInt(cleanHex.substr(4, 2), 16);
+ }
+
+ return `${r}, ${g}, ${b}`;
+};
+
+/**
+ * Mixes two hex colors by a given weight percentage
+ * @param baseColor Base color (e.g., '#0054e9')
+ * @param mixColor Color to mix in (e.g., '#000000' or '#fff')
+ * @param weight Weight percentage as string - how much of mixColor to mix into baseColor (e.g., '12%')
+ * @returns Mixed hex color
+ */
+export const mix = (baseColor: string, mixColor: string, weight: string): string => {
+ // Parse weight percentage
+ const w = parseFloat(weight.replace('%', '')) / 100;
+
+ // Parse hex colors
+ const parseHex = (hex: string): [number, number, number] => {
+ const cleanHex = hex.replace('#', '');
+
+ // Short hex format like 'fff' → expand to 'ffffff'
+ if (cleanHex.length === 3) {
+ return [
+ parseInt(cleanHex[0] + cleanHex[0], 16),
+ parseInt(cleanHex[1] + cleanHex[1], 16),
+ parseInt(cleanHex[2] + cleanHex[2], 16),
+ ];
+ // Full hex format like 'ffffff'
+ } else {
+ return [
+ parseInt(cleanHex.substr(0, 2), 16),
+ parseInt(cleanHex.substr(2, 2), 16),
+ parseInt(cleanHex.substr(4, 2), 16),
+ ];
+ }
+ };
+
+ // Parse both colors
+ const [baseR, baseG, baseB] = parseHex(baseColor);
+ const [mixR, mixG, mixB] = parseHex(mixColor);
+
+ // Mix mixColor into baseColor by weight
+ const r = Math.round(baseR * (1 - w) + mixR * w);
+ const g = Math.round(baseG * (1 - w) + mixG * w);
+ const b = Math.round(baseB * (1 - w) + mixB * w);
+
+ const toHex = (n: number) => n.toString(16).padStart(2, '0');
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+};
From 5435bb72473d3b75846cc9023c39462d1e21099a Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Tue, 23 Sep 2025 16:13:50 -0400
Subject: [PATCH 04/22] feat(themes): remove old $colors map and functions and
use new tokens
---
core/src/css/core.scss | 18 --
core/src/css/ionic/core.ionic.scss | 13 -
core/src/themes/functions.color.scss | 223 +++---------------
core/src/themes/ionic/ionic.globals.scss | 1 -
.../src/themes/ionic/ionic.theme.default.scss | 200 ----------------
core/src/themes/native/native.globals.scss | 1 -
.../themes/native/native.theme.default.scss | 113 ---------
7 files changed, 30 insertions(+), 539 deletions(-)
diff --git a/core/src/css/core.scss b/core/src/css/core.scss
index 3d7a727592e..5d1aa62b4b9 100644
--- a/core/src/css/core.scss
+++ b/core/src/css/core.scss
@@ -4,24 +4,6 @@
@import "../components/menu/menu.md.vars";
@import "../components/modal/modal.native.vars";
-// Ionic Colors
-// --------------------------------------------------
-
-:root {
- /**
- * Set the theme colors from the
- * `native.theme.default.scss` file.
- */
- @include set-theme-colors($colors);
- @include generate-color-variables();
-
- @each $color-name, $value in $colors {
- .ion-color-#{$color-name} {
- @include generate-color($color-name);
- }
- }
-}
-
// Ionic Font Family
// --------------------------------------------------
diff --git a/core/src/css/ionic/core.ionic.scss b/core/src/css/ionic/core.ionic.scss
index a81974eab6d..5ceea8d614a 100644
--- a/core/src/css/ionic/core.ionic.scss
+++ b/core/src/css/ionic/core.ionic.scss
@@ -4,19 +4,6 @@
// --------------------------------------------------
:root {
- /**
- * Set the theme colors from the
- * `ionic.theme.default.scss` file.
- */
- @include globals.set-theme-colors(globals.$ionic-colors);
- @include globals.generate-color-variables();
-
- @each $color-name, $value in globals.$ionic-colors {
- .ion-color-#{$color-name} {
- @include globals.generate-color($color-name);
- }
- }
-
/* Default background color of all components to default background surface token */
--background: #{globals.$ion-bg-surface-default};
}
diff --git a/core/src/themes/functions.color.scss b/core/src/themes/functions.color.scss
index a48b8e18c09..5e702be1c65 100644
--- a/core/src/themes/functions.color.scss
+++ b/core/src/themes/functions.color.scss
@@ -1,11 +1,5 @@
@use "sass:map";
-// Set the theme colors map to be used by the color functions
-// --------------------------------------------------------------------------------------------
-@mixin set-theme-colors($colorsMap) {
- $theme-colors: $colorsMap !global;
-}
-
// Gets the active color's css variable from a variation. Alpha is optional.
// --------------------------------------------------------------------------------------------
// Example usage:
@@ -25,50 +19,48 @@
// Gets the specific color's css variable from the name and variation. Alpha/rgb are optional.
// --------------------------------------------------------------------------------------------
// Example usage:
-// ion-color(primary, base) => var(--ion-color-primary, #3880ff)
+// ion-color(primary, base) => var(--ion-color-primary, var(--ion-color-primary-bold))
// ion-color(secondary, contrast) => var(--ion-color-secondary-contrast)
-// ion-color(primary, base, 0.5) => rgba(var(--ion-color-primary-rgb, 56, 128, 255), 0.5)
+// ion-color(primary, base, 0.5) => rgba(var(--ion-color-primary-rgb), 0.5)
+// ion-color(primary, base, null, true) => var(--ion-color-primary-rgb)
+// ion-color(primary, base, null, null, true) => var(--ion-color-primary-subtle)
// --------------------------------------------------------------------------------------------
@function ion-color($name, $variation, $alpha: null, $rgb: null, $subtle: false) {
- @if not($theme-colors) {
- @error 'No theme colors set. Please make sure to call set-theme-colors($colorsMap) before using ion-color()';
- }
-
- $values: map.get($theme-colors, $name);
- $values: map.get($values, if($subtle, subtle, bold));
-
- $value: map.get($values, $variation);
-
- // TODO(FW-6417): this can be removed when foreground is required
- // Fallback to "base" variant when "foreground" variant is undefined
- @if ($variation == foreground and $value == null) {
- $variation: base;
- $value: map.get($values, $variation);
- }
-
- // If the color requested is subtle we return `--ion-color-{color}-subtle-contrast`,
- // otherwise we return `--ion-color-{color}-contrast`.
- $variable: if($subtle, "--ion-color-#{$name}-subtle-#{$variation}", "--ion-color-#{$name}-#{$variation}");
-
- // If the variation being used is "base", we do not include the variant.
- // If the color requested is subtle we return `--ion-color-{color}-subtle`,
- // otherwise we return `--ion-color-{color}`.
- @if ($variation == base) {
- $variable: if($subtle, "--ion-color-#{$name}-subtle", "--ion-color-#{$name}");
+ // Build base variable name
+ $base-variable: if($subtle, "--ion-color-#{$name}-subtle", "--ion-color-#{$name}");
+ $variation-suffix: if($variation == base, "", "-#{$variation}");
+ $variable: "#{$base-variable}#{$variation-suffix}";
+
+ // Build fallback variable name (only for bold colors)
+ $fallback-variable: null;
+ @if (not $subtle) {
+ $fallback-base: "--ion-color-#{$name}-bold";
+ $fallback-variable: "#{$fallback-base}#{$variation-suffix}";
}
+ // Handle alpha transparency
@if ($alpha) {
- $value: color-to-rgb-list($value);
+ $rgb-var: "#{$variable}-rgb";
+ $fallback-rgb: if($fallback-variable, "#{$fallback-variable}-rgb", null);
- @return rgba(var(#{$variable}-rgb, $value), $alpha);
+ @if ($fallback-rgb) {
+ @return rgba(var(#{$rgb-var}, var(#{$fallback-rgb})), $alpha);
+ } @else {
+ @return rgba(var(#{$rgb-var}), $alpha);
+ }
}
+ // Handle RGB variables
@if ($rgb) {
- $value: color-to-rgb-list($value);
- $variable: #{$variable}-rgb;
+ $variable: "#{$variable}-rgb";
+ $fallback-variable: if($fallback-variable, "#{$fallback-variable}-rgb", null);
}
- @return var(#{$variable}, $value);
+ @if ($fallback-variable) {
+ @return var(#{$variable}, var(#{$fallback-variable}));
+ } @else {
+ @return var(#{$variable});
+ }
}
// Mixes a color with black to create its shade.
@@ -97,158 +89,3 @@
}
@return #{red($color)}, #{green($color)}, #{blue($color)};
}
-
-// Generates color variants for the specified color based on the
-// colors map for whichever hue is passed (bold, subtle).
-// --------------------------------------------------------------------------------------------
-// Example usage (bold):
-// .ion-color-primary {
-// @include generate-color-variants("primary");
-// }
-//
-// Example output (bold):
-// .ion-color-primary {
-// --ion-color-base: var(--ion-color-primary-base, #105cef) !important;
-// --ion-color-base-rgb: var(--ion-color-primary-base-rgb, 16, 92, 239) !important;
-// --ion-color-contrast: var(--ion-color-primary-contrast, #fff) !important;
-// --ion-color-contrast-rgb: var(--ion-color-primary-contrast-rgb, 255, 255, 255) !important;
-// --ion-color-shade: var(--ion-color-primary-shade, #0f54da) !important;
-// --ion-color-tint: var(--ion-color-primary-tint, #94a5f4) !important;
-// }
-// --------------------------------------------------------------------------------------------
-// Example usage (subtle):
-// .ion-color-primary {
-// @include generate-color-variants("primary", "subtle")
-// }
-//
-// Example output (subtle):
-// .ion-color-primary {
-// --ion-color-subtle-base: var(--ion-color-primary-subtle-base, #f2f4fd) !important;
-// --ion-color-subtle-base-rgb: var(--ion-color-primary-subtle-base-rgb, 242, 244, 253) !important;
-// --ion-color-subtle-contrast: var(--ion-color-primary-subtle-contrast, #105cef) !important;
-// --ion-color-subtle-contrast-rgb: var(--ion-color-primary-subtle-contrast-rgb, 16, 92, 239) !important;
-// --ion-color-subtle-shade: var(--ion-color-primary-subtle-shade, #d0d7fa) !important;
-// --ion-color-subtle-tint: var(--ion-color-primary-subtle-tint, #e9ecfc) !important;
-// }
-// --------------------------------------------------------------------------------------------
-@mixin generate-color-variants($color-name, $hue: "bold") {
- @if not($theme-colors) {
- @error 'No theme colors set. Please make sure to call set-theme-colors($colorsMap) before using ion-color()';
- }
-
- // Grab the different hue color maps for the
- // specified color and then grab the map of color variants
- $hue-colors: map.get($theme-colors, $color-name);
- $color-variants: map.get($hue-colors, $hue);
-
- $prefix: if($hue == "subtle", "-subtle", "");
-
- // TODO(FW-6417) this @if can be removed if we add subtle colors for ios and md
- // Only proceed if the color variants exist
- @if $color-variants {
- // Grab the individual color variants
- $base: map.get($color-variants, base);
- $base-rgb: map.get($color-variants, base-rgb);
- $contrast: map.get($color-variants, contrast);
- $contrast-rgb: map.get($color-variants, contrast-rgb);
- $shade: map.get($color-variants, shade);
- $tint: map.get($color-variants, tint);
- $foreground: map.get($color-variants, foreground);
-
- // Generate CSS variables dynamically
- --ion-color#{$prefix}-base: var(--ion-color-#{$color-name}#{$prefix}, #{$base}) !important;
- --ion-color#{$prefix}-base-rgb: var(--ion-color-#{$color-name}#{$prefix}-rgb, #{$base-rgb}) !important;
- --ion-color#{$prefix}-contrast: var(--ion-color-#{$color-name}#{$prefix}-contrast, #{$contrast}) !important;
- --ion-color#{$prefix}-contrast-rgb: var(
- --ion-color-#{$color-name}#{$prefix}-contrast-rgb,
- #{$contrast-rgb}
- ) !important;
- --ion-color#{$prefix}-shade: var(--ion-color-#{$color-name}#{$prefix}-shade, #{$shade}) !important;
- --ion-color#{$prefix}-tint: var(--ion-color-#{$color-name}#{$prefix}-tint, #{$tint}) !important;
- // TODO(FW-6417): remove the fallback variable when the foreground variable is
- // required by all palettes for all themes:
- // --ion-color#{$prefix}-foreground: var(--ion-color-#{$color-name}#{$prefix}-foreground, #{$foreground}) !important;
- --ion-color#{$prefix}-foreground: var(
- --ion-color-#{$color-name}#{$prefix}-foreground,
- var(--ion-color-#{$color-name}#{$prefix}, #{$foreground})
- ) !important;
- }
-}
-
-// Generates both bold and subtle color variables
-// for the specified color in the colors map.
-// --------------------------------------------------------------------------------------------
-@mixin generate-color($color-name) {
- @include generate-color-variants($color-name);
- @include generate-color-variants($color-name, "subtle");
-}
-
-// Generates color variables for all colors in the colors map for both hues (bold, subtle).
-// --------------------------------------------------------------------------------------------
-// Example usage:
-// :root {
-// generate-color-variables()
-// }
-//
-// Example output:
-// :root {
-// --ion-color-primary: #105cef;
-// --ion-color-primary-rgb: 16, 92, 239;
-// --ion-color-primary-contrast: #ffffff;
-// --ion-color-primary-contrast-rgb: 255, 255, 255;
-// --ion-color-primary-shade: #0f54da;
-// --ion-color-primary-tint: #94a5f4;
-// --ion-color-primary-foreground: #105cef;
-// --ion-color-primary-subtle: #f2f4fd;
-// --ion-color-primary-subtle-rgb: 242, 244, 253;
-// --ion-color-primary-subtle-contrast: #105cef;
-// --ion-color-primary-subtle-contrast-rgb: 16, 92, 239;
-// --ion-color-primary-subtle-shade: #d0d7fa;
-// --ion-color-primary-subtle-tint: #e9ecfc;
-// --ion-color-primary-foreground: #105cef;
-// ...
-// --ion-color-dark: #292929;
-// --ion-color-dark-rgb: 41, 41, 41;
-// --ion-color-dark-contrast: #ffffff;
-// --ion-color-dark-contrast-rgb: 255, 255, 255;
-// --ion-color-dark-shade: #242424;
-// --ion-color-dark-tint: #4e4e4e;
-// --ion-color-dark-foreground: #242424;
-// --ion-color-dark-subtle: #f5f5f5;
-// --ion-color-dark-subtle-rgb: 245, 245, 245;
-// --ion-color-dark-subtle-contrast: #292929;
-// --ion-color-dark-subtle-contrast-rgb: 41, 41, 41;
-// --ion-color-dark-subtle-shade: #e0e0e0;
-// --ion-color-dark-subtle-tint: #efefef;
-// --ion-color-dark-subtle-foreground: #242424;
-// }
-// --------------------------------------------------------------------------------------------
-@mixin generate-color-variables() {
- @if not($theme-colors) {
- @error 'No theme colors set. Please make sure to call set-theme-colors($colorsMap) before using ion-color().';
- }
-
- @each $color-name, $value in $theme-colors {
- @each $hue in (bold, subtle) {
- $colors: map.get($value, $hue);
-
- @if $colors != null {
- $prefix: if($hue == subtle, "-subtle", "");
-
- --ion-color-#{$color-name}#{$prefix}: #{map.get($colors, base)};
- --ion-color-#{$color-name}#{$prefix}-rgb: #{map.get($colors, base-rgb)};
- --ion-color-#{$color-name}#{$prefix}-contrast: #{map.get($colors, contrast)};
- --ion-color-#{$color-name}#{$prefix}-contrast-rgb: #{map.get($colors, contrast-rgb)};
- --ion-color-#{$color-name}#{$prefix}-shade: #{map.get($colors, shade)};
- --ion-color-#{$color-name}#{$prefix}-tint: #{map.get($colors, tint)};
- // TODO(FW-6417): this "if" can be removed when foreground is defined for ios/md
- // themes. It should not be added until we want foreground to be required for
- // ios and md because this will be a breaking change, requiring users to add
- // `--ion-color-{color}-foreground` in order to override the default colors
- @if (map.get($colors, foreground)) {
- --ion-color-#{$color-name}#{$prefix}-foreground: #{map.get($colors, foreground)};
- }
- }
- }
- }
-}
diff --git a/core/src/themes/ionic/ionic.globals.scss b/core/src/themes/ionic/ionic.globals.scss
index 17d0169994b..4b93206b688 100644
--- a/core/src/themes/ionic/ionic.globals.scss
+++ b/core/src/themes/ionic/ionic.globals.scss
@@ -16,4 +16,3 @@
// Default Theme
@use "./ionic.theme.default" as ionicTheme;
@forward "./ionic.theme.default";
-@include color.set-theme-colors(ionicTheme.$ionic-colors);
diff --git a/core/src/themes/ionic/ionic.theme.default.scss b/core/src/themes/ionic/ionic.theme.default.scss
index 267a128206f..30e6ca36c19 100644
--- a/core/src/themes/ionic/ionic.theme.default.scss
+++ b/core/src/themes/ionic/ionic.theme.default.scss
@@ -7,206 +7,6 @@
// between modes. This should only include variables
// used to theme the application colors.
-// Default Ionic Colors
-// -------------------------------------------------------------------------------------------
-// Color map should provide
-// - bold: a map of the bold color variations
-// - subtle: a map of the subtle color variations
-//
-// Each hue color map should provide
-// - base: The main color used for backgrounds
-// - base-rgb: The base color in RGB format
-// - contrast: A color that ensures readable text on the base color
-// - contrast-rgb: The contrast color in RGB format
-// - shade: A darker variant of the base color, used for pressed/active states
-// - tint: A lighter variant of the base color, used for ?
-// - foreground: The main color used for text and foreground elements
-
-// TODO(ROU-10778, ROU-10875): Sync the color names to the design system of
-// ios and md. This will allow us to have a single color map.
-$ionic-colors: (
- primary: (
- bold: (
- base: globals.$ion-bg-primary-base-default,
- base-rgb: globals.$ion-bg-primary-base-default-rgb,
- contrast: globals.$ion-text-inverse,
- contrast-rgb: globals.$ion-text-inverse-rgb,
- shade: globals.$ion-bg-primary-base-press,
- tint: globals.$ion-semantics-primary-600,
- foreground: globals.$ion-text-primary,
- ),
- subtle: (
- base: globals.$ion-bg-primary-subtle-default,
- base-rgb: globals.$ion-bg-primary-subtle-default-rgb,
- contrast: globals.$ion-text-primary,
- contrast-rgb: globals.$ion-text-primary-rgb,
- shade: globals.$ion-bg-primary-subtle-press,
- tint: globals.$ion-semantics-primary-200,
- foreground: globals.$ion-text-primary,
- ),
- ),
- secondary: (
- bold: (
- base: globals.$ion-bg-info-base-default,
- base-rgb: globals.$ion-bg-info-base-default-rgb,
- contrast: globals.$ion-text-inverse,
- contrast-rgb: globals.$ion-text-inverse-rgb,
- shade: globals.$ion-bg-info-base-press,
- tint: globals.$ion-semantics-info-700,
- foreground: globals.$ion-text-info,
- ),
- subtle: (
- base: globals.$ion-bg-info-subtle-default,
- base-rgb: globals.$ion-bg-info-subtle-default-rgb,
- contrast: globals.$ion-text-info,
- contrast-rgb: globals.$ion-text-info-rgb,
- shade: globals.$ion-bg-info-subtle-press,
- tint: globals.$ion-semantics-info-200,
- foreground: globals.$ion-text-info,
- ),
- ),
- tertiary: (
- bold: (
- base: globals.$ion-primitives-violet-700,
- base-rgb: globals.$ion-primitives-violet-700-rgb,
- contrast: globals.$ion-text-inverse,
- contrast-rgb: globals.$ion-text-inverse-rgb,
- shade: globals.$ion-primitives-violet-800,
- tint: globals.$ion-primitives-violet-600,
- foreground: globals.$ion-primitives-violet-700,
- ),
- subtle: (
- base: globals.$ion-primitives-violet-100,
- base-rgb: globals.$ion-primitives-violet-100-rgb,
- contrast: globals.$ion-primitives-violet-700,
- contrast-rgb: globals.$ion-primitives-violet-700-rgb,
- shade: globals.$ion-primitives-violet-300,
- tint: globals.$ion-primitives-violet-200,
- foreground: globals.$ion-primitives-violet-700,
- ),
- ),
- success: (
- bold: (
- base: globals.$ion-bg-success-base-default,
- base-rgb: globals.$ion-bg-success-base-default-rgb,
- contrast: globals.$ion-text-inverse,
- contrast-rgb: globals.$ion-text-inverse-rgb,
- shade: globals.$ion-bg-success-base-press,
- tint: globals.$ion-semantics-success-800,
- foreground: globals.$ion-text-success,
- ),
- subtle: (
- base: globals.$ion-bg-success-subtle-default,
- base-rgb: globals.$ion-bg-success-subtle-default-rgb,
- contrast: globals.$ion-text-success,
- contrast-rgb: globals.$ion-text-success-rgb,
- shade: globals.$ion-bg-success-subtle-press,
- tint: globals.$ion-semantics-success-200,
- foreground: globals.$ion-text-success,
- ),
- ),
- warning: (
- bold: (
- base: globals.$ion-bg-warning-base-default,
- base-rgb: globals.$ion-bg-warning-base-default-rgb,
- contrast: globals.$ion-text-default,
- contrast-rgb: globals.$ion-text-default-rgb,
- shade: globals.$ion-bg-warning-base-press,
- tint: globals.$ion-primitives-yellow-300,
- foreground: globals.$ion-text-warning,
- ),
- subtle: (
- base: globals.$ion-bg-warning-subtle-default,
- base-rgb: globals.$ion-bg-warning-subtle-default-rgb,
- contrast: globals.$ion-text-warning,
- contrast-rgb: globals.$ion-text-warning-rgb,
- shade: globals.$ion-bg-warning-subtle-press,
- tint: globals.$ion-primitives-yellow-100,
- foreground: globals.$ion-text-warning,
- ),
- ),
- danger: (
- bold: (
- base: globals.$ion-bg-danger-base-default,
- base-rgb: globals.$ion-bg-danger-base-default-rgb,
- contrast: globals.$ion-text-inverse,
- contrast-rgb: globals.$ion-text-inverse-rgb,
- shade: globals.$ion-bg-danger-base-press,
- tint: globals.$ion-semantics-danger-700,
- foreground: globals.$ion-text-danger,
- ),
- subtle: (
- base: globals.$ion-bg-danger-subtle-default,
- base-rgb: globals.$ion-bg-danger-subtle-default-rgb,
- contrast: globals.$ion-text-danger,
- contrast-rgb: globals.$ion-text-danger-rgb,
- shade: globals.$ion-bg-danger-subtle-press,
- tint: globals.$ion-semantics-danger-200,
- foreground: globals.$ion-text-danger,
- ),
- ),
- light: (
- bold: (
- base: globals.$ion-bg-neutral-base-default,
- base-rgb: globals.$ion-bg-neutral-base-default-rgb,
- contrast: globals.$ion-text-default,
- contrast-rgb: globals.$ion-text-default-rgb,
- shade: globals.$ion-primitives-neutral-600,
- tint: globals.$ion-primitives-neutral-400,
- foreground: globals.$ion-text-default,
- ),
- subtle: (
- base: globals.$ion-bg-neutral-subtlest-default,
- base-rgb: globals.$ion-bg-neutral-subtlest-default-rgb,
- contrast: globals.$ion-text-default,
- contrast-rgb: globals.$ion-text-default-rgb,
- shade: globals.$ion-bg-neutral-subtlest-press,
- tint: globals.$ion-primitives-neutral-100,
- foreground: globals.$ion-text-default,
- ),
- ),
- medium: (
- bold: (
- base: globals.$ion-bg-neutral-bold-default,
- base-rgb: globals.$ion-bg-neutral-bold-default-rgb,
- contrast: globals.$ion-text-inverse,
- contrast-rgb: globals.$ion-text-inverse-rgb,
- shade: globals.$ion-bg-neutral-bold-press,
- tint: globals.$ion-primitives-neutral-900,
- foreground: globals.$ion-text-default,
- ),
- subtle: (
- base: globals.$ion-bg-neutral-subtle-default,
- base-rgb: globals.$ion-bg-neutral-subtle-default-rgb,
- contrast: globals.$ion-text-subtlest,
- contrast-rgb: globals.$ion-text-subtlest-rgb,
- shade: globals.$ion-bg-neutral-subtle-press,
- tint: globals.$ion-primitives-neutral-100,
- foreground: globals.$ion-text-default,
- ),
- ),
- dark: (
- bold: (
- base: globals.$ion-bg-neutral-boldest-default,
- base-rgb: globals.$ion-bg-neutral-boldest-default-rgb,
- contrast: globals.$ion-text-inverse,
- contrast-rgb: globals.$ion-text-inverse-rgb,
- shade: globals.$ion-bg-neutral-boldest-press,
- tint: globals.$ion-primitives-neutral-1100,
- foreground: globals.$ion-text-default,
- ),
- subtle: (
- base: globals.$ion-bg-neutral-subtle-default,
- base-rgb: globals.$ion-bg-neutral-subtle-default-rgb,
- contrast: globals.$ion-text-subtle,
- contrast-rgb: globals.$ion-text-subtle-rgb,
- shade: globals.$ion-bg-neutral-subtle-press,
- tint: globals.$ion-primitives-neutral-100,
- foreground: globals.$ion-text-default,
- ),
- ),
-);
-
// Ionic Tabs & Tab Bar
// --------------------------------------------------
diff --git a/core/src/themes/native/native.globals.scss b/core/src/themes/native/native.globals.scss
index 264098e9759..b87ed699bdf 100644
--- a/core/src/themes/native/native.globals.scss
+++ b/core/src/themes/native/native.globals.scss
@@ -13,7 +13,6 @@
// Default Theme
@import "./native.theme.default";
-@include set-theme-colors($colors);
// Default General
// --------------------------------------------------
diff --git a/core/src/themes/native/native.theme.default.scss b/core/src/themes/native/native.theme.default.scss
index 22edeb872f6..db4819c8a94 100644
--- a/core/src/themes/native/native.theme.default.scss
+++ b/core/src/themes/native/native.theme.default.scss
@@ -4,119 +4,6 @@
// between modes. This should only include variables
// used to theme the application colors.
-// Default Ionic Colors
-// -------------------------------------------------------------------------------------------
-// Color map should provide
-// - base: The main color used for backgrounds
-// - base-rgb: The base color in RGB format
-// - contrast: A color that ensures readable text on the base color
-// - contrast-rgb: The contrast color in RGB format
-// - shade: 12% darker version of the base color (mix with black), used for pressed/active states
-// - tint: 10% lighter version of the base color (mix with white), used for focused/hover states
-
-$primary: #0054e9;
-$secondary: #0163aa;
-$tertiary: #6030ff;
-$success: #2dd55b;
-$warning: #ffc409;
-$danger: #c5000f;
-$light: #f4f5f8;
-$medium: #636469;
-$dark: #222428;
-
-$colors: (
- primary: (
- bold: (
- base: $primary,
- base-rgb: color-to-rgb-list($primary),
- contrast: #fff,
- contrast-rgb: color-to-rgb-list(#fff),
- shade: get-color-shade($primary),
- tint: get-color-tint($primary),
- ),
- ),
- secondary: (
- bold: (
- base: $secondary,
- base-rgb: color-to-rgb-list($secondary),
- contrast: #fff,
- contrast-rgb: color-to-rgb-list(#fff),
- shade: get-color-shade($secondary),
- tint: get-color-tint($secondary),
- ),
- ),
- tertiary: (
- bold: (
- base: $tertiary,
- base-rgb: color-to-rgb-list($tertiary),
- contrast: #fff,
- contrast-rgb: color-to-rgb-list(#fff),
- shade: get-color-shade($tertiary),
- tint: get-color-tint($tertiary),
- ),
- ),
- success: (
- bold: (
- base: $success,
- base-rgb: color-to-rgb-list($success),
- contrast: #000,
- contrast-rgb: color-to-rgb-list(#000),
- shade: get-color-shade($success),
- tint: get-color-tint($success),
- ),
- ),
- warning: (
- bold: (
- base: $warning,
- base-rgb: color-to-rgb-list($warning),
- contrast: #000,
- contrast-rgb: color-to-rgb-list(#000),
- shade: get-color-shade($warning),
- tint: get-color-tint($warning),
- ),
- ),
- danger: (
- bold: (
- base: $danger,
- base-rgb: color-to-rgb-list($danger),
- contrast: #fff,
- contrast-rgb: color-to-rgb-list(#fff),
- shade: get-color-shade($danger),
- tint: get-color-tint($danger),
- ),
- ),
- light: (
- bold: (
- base: $light,
- base-rgb: color-to-rgb-list($light),
- contrast: #000,
- contrast-rgb: color-to-rgb-list(#000),
- shade: get-color-shade($light),
- tint: get-color-tint($light),
- ),
- ),
- medium: (
- bold: (
- base: $medium,
- base-rgb: color-to-rgb-list($medium),
- contrast: #fff,
- contrast-rgb: color-to-rgb-list(#fff),
- shade: get-color-shade($medium),
- tint: get-color-tint($medium),
- ),
- ),
- dark: (
- bold: (
- base: $dark,
- base-rgb: color-to-rgb-list($dark),
- contrast: #fff,
- contrast-rgb: color-to-rgb-list(#fff),
- shade: get-color-shade($dark),
- tint: get-color-tint($dark),
- ),
- ),
-);
-
// Default Foreground and Background Colors
// -------------------------------------------------------------------------------------------
// Used internally to calculate the default steps
From 2183a24b3c2db3c077d3ba926da05c057763b562 Mon Sep 17 00:00:00 2001
From: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Date: Tue, 23 Sep 2025 16:40:36 -0400
Subject: [PATCH 05/22] style: lint
---
core/src/themes/test/basic/index.html | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/core/src/themes/test/basic/index.html b/core/src/themes/test/basic/index.html
index ec328d34dd7..81bfba8139f 100644
--- a/core/src/themes/test/basic/index.html
+++ b/core/src/themes/test/basic/index.html
@@ -31,18 +31,18 @@
margin: 0;
}
- [class^="ion-scaling-"],
- [class^="ion-spacing-"],
- [class^="ion-radii-"],
- [class^="ion-border-width-"] {
+ [class^='ion-scaling-'],
+ [class^='ion-spacing-'],
+ [class^='ion-radii-'],
+ [class^='ion-border-width-'] {
margin: 10px;
}
- [class^="ion-scaling-"] {
+ [class^='ion-scaling-'] {
background: #ededed;
}
- [class^="ion-spacing-"] {
+ [class^='ion-spacing-'] {
background: #e0ee99;
}
@@ -50,12 +50,12 @@
background: #ededed;
}
- [class^="ion-radii-"] {
+ [class^='ion-radii-'] {
border-width: 2px;
}
- [class^="ion-radii-"],
- [class^="ion-border-width-"] {
+ [class^='ion-radii-'],
+ [class^='ion-border-width-'] {
display: inline-flex;
align-items: center;
justify-content: center;
@@ -282,7 +282,7 @@ Border Width
xxl
-
+