diff --git a/BREAKING.md b/BREAKING.md
index 6cd1cd3e06a..61719f311ec 100644
--- a/BREAKING.md
+++ b/BREAKING.md
@@ -26,6 +26,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- [Item Divider](#version-9x-item-divider)
- [Radio Group](#version-9x-radio-group)
- [Spinner](#version-9x-spinner)
+ - [Text](#version-9x-text)
- [Textarea](#version-9x-textarea)
- [Thumbnail](#version-9x-thumbnail)
@@ -290,6 +291,28 @@ Additionally, the `radio-group-wrapper` div element has been removed, causing sl
- `.spinner-[spinner-name]` → `.spinner-name-[spinner-name]`
- Specific theme classes (e.g., `ion-spinner.md`) are no longer supported. Style modifications based on the active theme must be implemented using theme tokens rather than direct class targeting.
+
Text
+
+The following breaking changes apply to `ion-text`:
+
+1. The color applied by the `color` prop is now driven by the centralized Ionic Theming system, scoped to the new `hue` property.
+2. Theme classes (`ion-text.md`, `ion-text.ios`) are no longer supported.
+
+
New `hue` property and color tokens
+
+A new `hue` property selects between vibrant and muted color variants. It defaults to `"bold"`, which preserves prior behavior when `color` is set.
+
+When `color` is set, the text color now reads from a token instead of `--ion-color-base` directly. Global overrides should use the theme tokens; component-specific overrides use the corresponding CSS variables:
+
+| Hue | Token (global) | CSS variable (component-specific) |
+|---|---|---|
+| `bold` | `IonText.hue.bold.semantic.default.color` | `--ion-text-hue-bold-semantic-default-color` |
+| `subtle` | `IonText.hue.subtle.semantic.default.color` | `--ion-text-hue-subtle-semantic-default-color` |
+
+
Theme classes
+
+Remove any instances that target the theme classes: `ion-text.md`, `ion-text.ios`.
+
Textarea
Converted `ion-textarea` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM).
diff --git a/core/api.txt b/core/api.txt
index 2d53d4befa4..9da256cdb9f 100644
--- a/core/api.txt
+++ b/core/api.txt
@@ -2707,8 +2707,10 @@ ion-tabs,event,ionTabsWillChange,{ tab: string; },false
ion-text,shadow
ion-text,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true
+ion-text,prop,hue,"bold" | "subtle" | undefined,undefined,false,false
ion-text,prop,mode,"ios" | "md",undefined,false,false
-ion-text,prop,theme,"ios" | "md" | "ionic",undefined,false,false
+ion-text,css-prop,--ion-text-hue-bold-semantic-default-color
+ion-text,css-prop,--ion-text-hue-subtle-semantic-default-color
ion-textarea,shadow
ion-textarea,prop,autoGrow,boolean,false,false,true
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index a90ea6c6619..9d29fde767d 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -11,11 +11,12 @@ import { ActionSheetButton } from "./components/action-sheet/action-sheet-interf
import { OverlayEventDetail } from "./utils/overlays-interface";
import { IonicSafeString } from "./utils/sanitization";
import { AlertButton, AlertInput } from "./components/alert/alert-interface";
-import { IonBadgeHue, IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces";
+import { Hue } from "./themes/themes.interfaces";
+import { IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces";
import { RouteID, RouterDirection, RouterEventDetail, RouteWrite } from "./components/router/utils/interface";
import { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/breadcrumb-interface";
import { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface";
-import { IonChipFill, IonChipHue, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces";
+import { IonChipFill, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces";
import { ScrollBaseDetail, ScrollDetail } from "./components/content/content.interfaces";
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
import { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface";
@@ -54,11 +55,12 @@ export { ActionSheetButton } from "./components/action-sheet/action-sheet-interf
export { OverlayEventDetail } from "./utils/overlays-interface";
export { IonicSafeString } from "./utils/sanitization";
export { AlertButton, AlertInput } from "./components/alert/alert-interface";
-export { IonBadgeHue, IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces";
+export { Hue } from "./themes/themes.interfaces";
+export { IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from "./components/badge/badge.interfaces";
export { RouteID, RouterDirection, RouterEventDetail, RouteWrite } from "./components/router/utils/interface";
export { BreadcrumbCollapsedClickEventDetail } from "./components/breadcrumb/breadcrumb-interface";
export { CheckboxChangeEventDetail } from "./components/checkbox/checkbox-interface";
-export { IonChipFill, IonChipHue, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces";
+export { IonChipFill, IonChipShape, IonChipSize } from "./components/chip/chip.interfaces";
export { ScrollBaseDetail, ScrollDetail } from "./components/content/content.interfaces";
export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
export { GalleryColumns, GalleryGap } from "./components/gallery/gallery-interface";
@@ -477,7 +479,7 @@ export namespace Components {
/**
* Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset.
*/
- "hue"?: IonBadgeHue;
+ "hue"?: Hue;
/**
* The mode determines the platform behaviors of the component.
*/
@@ -884,7 +886,7 @@ export namespace Components {
/**
* Set to `"bold"` for a chip with vibrant, bold colors or to `"subtle"` for a chip with muted, subtle colors. Defaults to `"subtle"` if both the hue property and theme config are unset.
*/
- "hue"?: IonChipHue;
+ "hue"?: Hue;
/**
* The mode determines the platform behaviors of the component.
*/
@@ -4106,13 +4108,13 @@ export namespace Components {
*/
"color"?: Color;
/**
- * The mode determines the platform behaviors of the component.
+ * Set to `"bold"` for a text with vibrant, bold colors or to `"subtle"` for a text with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset.
*/
- "mode"?: "ios" | "md";
+ "hue"?: Hue;
/**
- * The theme determines the visual appearance of the component.
+ * The mode determines the platform behaviors of the component.
*/
- "theme"?: "ios" | "md" | "ionic";
+ "mode"?: "ios" | "md";
}
interface IonTextarea {
/**
@@ -6469,7 +6471,7 @@ declare namespace LocalJSX {
/**
* Set to `"bold"` for a badge with vibrant, bold colors or to `"subtle"` for a badge with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset.
*/
- "hue"?: IonBadgeHue;
+ "hue"?: Hue;
/**
* The mode determines the platform behaviors of the component.
*/
@@ -6911,7 +6913,7 @@ declare namespace LocalJSX {
/**
* Set to `"bold"` for a chip with vibrant, bold colors or to `"subtle"` for a chip with muted, subtle colors. Defaults to `"subtle"` if both the hue property and theme config are unset.
*/
- "hue"?: IonChipHue;
+ "hue"?: Hue;
/**
* The mode determines the platform behaviors of the component.
*/
@@ -10218,13 +10220,13 @@ declare namespace LocalJSX {
*/
"color"?: Color;
/**
- * The mode determines the platform behaviors of the component.
+ * Set to `"bold"` for a text with vibrant, bold colors or to `"subtle"` for a text with muted, subtle colors. Defaults to `"bold"` if both the hue property and theme config are unset.
*/
- "mode"?: "ios" | "md";
+ "hue"?: Hue;
/**
- * The theme determines the visual appearance of the component.
+ * The mode determines the platform behaviors of the component.
*/
- "theme"?: "ios" | "md" | "ionic";
+ "mode"?: "ios" | "md";
}
interface IonTextarea {
/**
@@ -10712,7 +10714,7 @@ declare namespace LocalJSX {
}
interface IonBadgeAttributes {
"color": Color;
- "hue": IonBadgeHue;
+ "hue": Hue;
"shape": IonBadgeShape;
"size": IonBadgeSize;
"vertical": IonBadgeVerticalPosition;
@@ -10800,7 +10802,7 @@ declare namespace LocalJSX {
"outline": boolean;
"fill": IonChipFill;
"disabled": boolean;
- "hue": IonChipHue;
+ "hue": Hue;
"shape": IonChipShape;
"size": IonChipSize;
}
@@ -11378,6 +11380,7 @@ declare namespace LocalJSX {
}
interface IonTextAttributes {
"color": Color;
+ "hue": Hue;
}
interface IonTextareaAttributes {
"color": Color;
diff --git a/core/src/components/badge/badge.interfaces.ts b/core/src/components/badge/badge.interfaces.ts
index 0c3d1b982d8..7e8faeacbe5 100644
--- a/core/src/components/badge/badge.interfaces.ts
+++ b/core/src/components/badge/badge.interfaces.ts
@@ -1,4 +1,4 @@
-import type { IonPadding } from '../../themes/themes.interfaces';
+import type { IonPadding, Hue } from '../../themes/themes.interfaces';
export type IonBadgeRecipe = {
font?: {
@@ -7,7 +7,7 @@ export type IonBadgeRecipe = {
// Hues
hue?: {
- [K in IonBadgeHue]?: IonBadgeStateDefinition & {
+ [K in Hue]?: IonBadgeStateDefinition & {
semantic?: IonBadgeStateDefinition;
};
};
@@ -80,13 +80,11 @@ type IonBadgeSizeDotDefinition = IonBadgeSizeDefinition & {
};
export type IonBadgeConfig = {
- hue?: IonBadgeHue;
+ hue?: Hue;
size?: IonBadgeSize;
shape?: IonBadgeShape;
};
-export const ION_BADGE_HUES = ['bold', 'subtle'] as const;
-export type IonBadgeHue = (typeof ION_BADGE_HUES)[number];
export const ION_BADGE_SHAPES = ['crisp', 'soft', 'round', 'rectangular'] as const;
export type IonBadgeShape = (typeof ION_BADGE_SHAPES)[number];
export const ION_BADGE_SIZES = ['small', 'medium', 'large'] as const;
diff --git a/core/src/components/badge/badge.tsx b/core/src/components/badge/badge.tsx
index f1025803254..f534030ac3c 100644
--- a/core/src/components/badge/badge.tsx
+++ b/core/src/components/badge/badge.tsx
@@ -4,8 +4,9 @@ import { createColorClasses } from '@utils/theme';
import { config } from '../../global/config';
import type { Color } from '../../interface';
+import type { Hue } from '../../themes/themes.interfaces';
-import type { IonBadgeHue, IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from './badge.interfaces';
+import type { IonBadgeShape, IonBadgeSize, IonBadgeVerticalPosition } from './badge.interfaces';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
@@ -31,7 +32,7 @@ export class Badge implements ComponentInterface {
*
* Defaults to `"bold"` if both the hue property and theme config are unset.
*/
- @Prop() hue?: IonBadgeHue;
+ @Prop() hue?: Hue;
/**
* Set to `"crisp"` for a badge with even slightly rounded corners,
@@ -84,8 +85,8 @@ export class Badge implements ComponentInterface {
* Gets the badge hue. Uses the `hue` property if set, otherwise
* checks the theme config and falls back to 'bold' if neither is provided.
*/
- get hueValue(): IonBadgeHue {
- const hueConfig = config.getObjectValue('IonBadge.hue', 'bold') as IonBadgeHue;
+ get hueValue(): Hue {
+ const hueConfig = config.getObjectValue('IonBadge.hue', 'bold') as Hue;
const hue = this.hue || hueConfig;
return hue;
diff --git a/core/src/components/badge/test/hue/badge.e2e.ts b/core/src/components/badge/test/hue/badge.e2e.ts
index 077b1f00512..ac76109f59f 100644
--- a/core/src/components/badge/test/hue/badge.e2e.ts
+++ b/core/src/components/badge/test/hue/badge.e2e.ts
@@ -1,7 +1,7 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
-import { ION_BADGE_HUES } from '../../../badge/badge.interfaces';
+import { HUES } from '../../../../themes/themes.interfaces';
/**
* This behavior does not vary across modes/directions.
@@ -10,7 +10,7 @@ import { ION_BADGE_HUES } from '../../../badge/badge.interfaces';
*/
configs({ directions: ['ltr'], modes: ['md', 'ionic-md'] }).forEach(({ config, screenshot, title }) => {
test.describe(title('badge: hue'), () => {
- ION_BADGE_HUES.forEach((hue) => {
+ HUES.forEach((hue) => {
test(`should render ${hue} badges`, async ({ page }) => {
await page.setContent(
`
diff --git a/core/src/components/chip/chip.interfaces.ts b/core/src/components/chip/chip.interfaces.ts
index 341a56a7faf..c66a56a1a3c 100644
--- a/core/src/components/chip/chip.interfaces.ts
+++ b/core/src/components/chip/chip.interfaces.ts
@@ -1,4 +1,4 @@
-import type { IonPadding, IonMargin } from '../../themes/themes.interfaces';
+import type { IonPadding, IonMargin, Hue } from '../../themes/themes.interfaces';
export type IonChipRecipe = {
letterSpacing?: string | number;
@@ -13,7 +13,7 @@ export type IonChipRecipe = {
// Hues with fills
hue?: {
- [K in IonChipHue]?: IonChipFillDefinition;
+ [K in Hue]?: IonChipFillDefinition;
};
// Sizes
@@ -111,12 +111,11 @@ type IonChipAvatarDefinition = IonChipMediaDefinition & {
export type IonChipConfig = {
fill?: IonChipFill;
- hue?: IonChipHue;
+ hue?: Hue;
size?: IonChipSize;
shape?: IonChipShape;
};
export type IonChipFill = 'outline' | 'solid';
-export type IonChipHue = 'bold' | 'subtle';
export type IonChipSize = 'small' | 'large';
export type IonChipShape = 'soft' | 'round' | 'rectangular';
diff --git a/core/src/components/chip/chip.tsx b/core/src/components/chip/chip.tsx
index 5b06b7adc02..efc5f5266f3 100644
--- a/core/src/components/chip/chip.tsx
+++ b/core/src/components/chip/chip.tsx
@@ -5,8 +5,9 @@ import { createColorClasses } from '@utils/theme';
import { config } from '../../global/config';
import type { Color } from '../../interface';
+import type { Hue } from '../../themes/themes.interfaces';
-import type { IonChipFill, IonChipHue, IonChipSize, IonChipShape } from './chip.interfaces';
+import type { IonChipFill, IonChipSize, IonChipShape } from './chip.interfaces';
/**
* @virtualProp {"ios" | "md"} mode - The mode determines the platform behaviors of the component.
@@ -54,7 +55,7 @@ export class Chip implements ComponentInterface {
*
* Defaults to `"subtle"` if both the hue property and theme config are unset.
*/
- @Prop() hue?: IonChipHue;
+ @Prop() hue?: Hue;
/**
* Set to `"soft"` for a chip with slightly rounded corners,
@@ -98,8 +99,8 @@ export class Chip implements ComponentInterface {
* Gets the chip hue. Uses the `hue` property if set, otherwise
* checks the theme config. Defaults to `subtle` if neither is set.
*/
- get hueValue(): IonChipHue {
- const hueConfig = config.getObjectValue('IonChip.hue', 'subtle') as IonChipHue;
+ get hueValue(): Hue {
+ const hueConfig = config.getObjectValue('IonChip.hue', 'subtle') as Hue;
const hue = this.hue || hueConfig;
return hue;
diff --git a/core/src/components/text/test/basic/text.e2e.ts b/core/src/components/text/test/basic/text.e2e.ts
index a98baa015d1..81fce3d4b8a 100644
--- a/core/src/components/text/test/basic/text.e2e.ts
+++ b/core/src/components/text/test/basic/text.e2e.ts
@@ -19,18 +19,5 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
const text = page.locator('ion-text');
await expect(text.nth(0)).toHaveScreenshot(screenshot(`text`));
});
- test('should render text with color prop', async ({ page }) => {
- await page.setContent(
- `
-
- The quick brown fox jumps over the lazy dog
-
- `,
- config
- );
-
- const text = page.locator('ion-text');
- await expect(text.nth(0)).toHaveScreenshot(screenshot(`text-color`));
- });
});
});
diff --git a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png
deleted file mode 100644
index f5fe40421a6..00000000000
Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Chrome-linux.png and /dev/null differ
diff --git a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png
deleted file mode 100644
index f3f11dd72c6..00000000000
Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Firefox-linux.png and /dev/null differ
diff --git a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png b/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png
deleted file mode 100644
index 6f05eece866..00000000000
Binary files a/core/src/components/text/test/basic/text.e2e.ts-snapshots/text-color-md-ltr-Mobile-Safari-linux.png and /dev/null differ
diff --git a/core/src/components/text/test/hue/index.html b/core/src/components/text/test/hue/index.html
new file mode 100644
index 00000000000..42bb9133fc4
--- /dev/null
+++ b/core/src/components/text/test/hue/index.html
@@ -0,0 +1,65 @@
+
+
+
+
+ Text - Hue
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Text - Hue
+
+
+
+
+
Text Hue: Bold
+
+
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+
+
+
Text Hue: Subtle
+
+
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+ The quick brown fox jumps over the lazy dog
+
+
+
+
+
diff --git a/core/src/components/text/test/hue/text.e2e.ts b/core/src/components/text/test/hue/text.e2e.ts
new file mode 100644
index 00000000000..415c2f3f919
--- /dev/null
+++ b/core/src/components/text/test/hue/text.e2e.ts
@@ -0,0 +1,22 @@
+import { expect } from '@playwright/test';
+import { configs, test } from '@utils/test/playwright';
+
+import { HUES } from '../../../../themes/themes.interfaces';
+
+/**
+ * This behavior does not vary across modes/directions.
+ */
+configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
+ test.describe(title('text: basic'), () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto(`/src/components/text/test/hue`, config);
+ });
+
+ HUES.forEach((hue) => {
+ test(`should render ${hue} text`, async ({ page }) => {
+ const text = page.locator(`#${hue}`);
+ await expect(text).toHaveScreenshot(screenshot(`text-hue-${hue}`));
+ });
+ });
+ });
+});
diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Chrome-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..896a649edcd
Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Firefox-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..35de690be75
Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Safari-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..551e96f9dfb
Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-bold-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Chrome-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Chrome-linux.png
new file mode 100644
index 00000000000..896a649edcd
Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Chrome-linux.png differ
diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Firefox-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Firefox-linux.png
new file mode 100644
index 00000000000..35de690be75
Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Firefox-linux.png differ
diff --git a/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Safari-linux.png b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Safari-linux.png
new file mode 100644
index 00000000000..551e96f9dfb
Binary files /dev/null and b/core/src/components/text/test/hue/text.e2e.ts-snapshots/text-hue-subtle-md-ltr-Mobile-Safari-linux.png differ
diff --git a/core/src/components/text/text.interfaces.ts b/core/src/components/text/text.interfaces.ts
new file mode 100644
index 00000000000..eeef7f69f4f
--- /dev/null
+++ b/core/src/components/text/text.interfaces.ts
@@ -0,0 +1,20 @@
+import type { Hue } from '../../themes/themes.interfaces';
+
+export type IonTextRecipe = {
+ hue?: {
+ [K in Hue]?: {
+ /** Any of the semantic colors like primary, secondary, etc. */
+ semantic?: {
+ default?: {
+ color?: string;
+ };
+ };
+ };
+ };
+};
+
+export type IonTextConfig = {
+ hue?: Hue;
+};
+
+export type IonTextHue = Hue;
diff --git a/core/src/components/text/text.scss b/core/src/components/text/text.scss
index d86de15d200..f1f0be8fbd7 100644
--- a/core/src/components/text/text.scss
+++ b/core/src/components/text/text.scss
@@ -1,8 +1,19 @@
-@import "../../themes/native/native.globals";
-
-// Text
+// Text: Common Styles
// --------------------------------------------------
-:host(.ion-color) {
- color: current-color(base);
+:host {
+ /**
+ * @prop --ion-text-hue-bold-semantic-default-color: Color of the `bold` hue when a semantic color is applied
+ * @prop --ion-text-hue-subtle-semantic-default-color: Color of the `subtle` hue when a semantic color is applied
+ */
+
+ color: inherit;
+}
+
+:host(.text-hue-bold.ion-color) {
+ color: var(--ion-text-hue-bold-semantic-default-color);
+}
+
+:host(.text-hue-subtle.ion-color) {
+ color: var(--ion-text-hue-subtle-semantic-default-color);
}
diff --git a/core/src/components/text/text.tsx b/core/src/components/text/text.tsx
index c3fab899fd8..d4b40825098 100644
--- a/core/src/components/text/text.tsx
+++ b/core/src/components/text/text.tsx
@@ -2,12 +2,12 @@ import type { ComponentInterface } from '@stencil/core';
import { Component, Host, Prop, h } from '@stencil/core';
import { createColorClasses } from '@utils/theme';
-import { getIonTheme } from '../../global/ionic-global';
+import { config } from '../../global/config';
import type { Color } from '../../interface';
+import type { Hue } from '../../themes/themes.interfaces';
/**
* @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-text',
@@ -22,12 +22,30 @@ export class Text implements ComponentInterface {
*/
@Prop({ reflect: true }) color?: Color;
+ /**
+ * Set to `"bold"` for a text with vibrant, bold colors or to `"subtle"` for
+ * a text with muted, subtle colors.
+ *
+ * Defaults to `"bold"` if both the hue property and theme config are unset.
+ */
+ @Prop() hue?: Hue;
+
+ /**
+ * Gets the text hue. Uses the `hue` property if set, otherwise
+ * checks the theme config and falls back to 'bold' if neither is provided.
+ */
+ get hueValue(): Hue {
+ const hueConfig = config.getObjectValue('IonText.hue', 'bold') as Hue;
+
+ return this.hue || hueConfig;
+ }
+
render() {
- const theme = getIonTheme(this);
+ const { hueValue } = this;
return (
diff --git a/core/src/themes/base/dark.tokens.ts b/core/src/themes/base/dark.tokens.ts
index 1b611610c66..643545da890 100644
--- a/core/src/themes/base/dark.tokens.ts
+++ b/core/src/themes/base/dark.tokens.ts
@@ -13,6 +13,7 @@ const colors = {
dark: '#f4f5f8',
};
+// TODO(FW-7558): Finalize color palette and update these color values as needed
export const darkTheme: DarkTheme = {
enabled: 'never',
color: {
diff --git a/core/src/themes/base/light.tokens.ts b/core/src/themes/base/light.tokens.ts
index f584b3279c7..0ff2482ed40 100644
--- a/core/src/themes/base/light.tokens.ts
+++ b/core/src/themes/base/light.tokens.ts
@@ -13,6 +13,7 @@ const colors = {
dark: '#222428',
};
+// TODO(FW-7558): Finalize color palette and update these color values as needed
export const lightTheme: LightTheme = {
color: {
primary: {
diff --git a/core/src/themes/ionic/default.tokens.ts b/core/src/themes/ionic/default.tokens.ts
index e6fd11c2815..f9a6bdf0a02 100644
--- a/core/src/themes/ionic/default.tokens.ts
+++ b/core/src/themes/ionic/default.tokens.ts
@@ -41,6 +41,10 @@ export const defaultTheme: DefaultTheme = {
IonSpinner: {
size: 'xsmall',
},
+
+ IonText: {
+ hue: 'bold',
+ },
},
},
@@ -831,6 +835,26 @@ export const defaultTheme: DefaultTheme = {
},
},
+ IonText: {
+ hue: {
+ bold: {
+ semantic: {
+ default: {
+ color: currentColor('foreground'),
+ },
+ },
+ },
+
+ subtle: {
+ semantic: {
+ default: {
+ color: currentColor('foreground', { subtle: true }),
+ },
+ },
+ },
+ },
+ },
+
IonThumbnail: {
height: 'var(--ion-scaling-xl)',
width: 'var(--ion-scaling-xl)',
diff --git a/core/src/themes/ionic/test/colors/theme.e2e.ts b/core/src/themes/ionic/test/colors/theme.e2e.ts
index 2adccbad0c0..fb5fa71ad85 100644
--- a/core/src/themes/ionic/test/colors/theme.e2e.ts
+++ b/core/src/themes/ionic/test/colors/theme.e2e.ts
@@ -66,7 +66,7 @@ const styleTestHelpers = `
configs({ modes: ['ionic-md'], directions: ['ltr'], palettes: ['light', 'dark'] }).forEach(({ config, title }) => {
const colors = ['primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', 'light', 'medium', 'dark'];
- // TODO: Re-enable this test once the colors have been finalized
+ // TODO(FW-7558): Re-enable this test once the colors have been finalized
test.describe.skip(title('palette colors: bold'), () => {
test.beforeEach(({ skip }) => {
skip.browser('firefox', 'Color contrast ratio is consistent across browsers');
@@ -134,7 +134,7 @@ configs({ modes: ['ionic-md'], directions: ['ltr'], palettes: ['light', 'dark']
}
});
- // TODO: Re-enable this test once the colors have been finalized
+ // TODO(FW-7558): Re-enable this test once the colors have been finalized
test.describe.skip(title('palette colors: subtle'), () => {
test.beforeEach(({ skip }) => {
skip.browser('firefox', 'Color contrast ratio is consistent across browsers');
diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts
index dcebf89e929..673459d44d3 100644
--- a/core/src/themes/ios/default.tokens.ts
+++ b/core/src/themes/ios/default.tokens.ts
@@ -43,6 +43,10 @@ export const defaultTheme: DefaultTheme = {
IonSpinner: {
size: 'medium',
},
+
+ IonText: {
+ hue: 'bold',
+ },
},
},
@@ -859,6 +863,26 @@ export const defaultTheme: DefaultTheme = {
},
},
+ IonText: {
+ hue: {
+ bold: {
+ semantic: {
+ default: {
+ color: currentColor('foreground'),
+ },
+ },
+ },
+
+ subtle: {
+ semantic: {
+ default: {
+ color: currentColor('foreground', { subtle: true }),
+ },
+ },
+ },
+ },
+ },
+
IonThumbnail: {
height: 'var(--ion-scaling-xxxl)',
width: 'var(--ion-scaling-xxxl)',
diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts
index 84a617f796f..f9411dc0429 100644
--- a/core/src/themes/md/default.tokens.ts
+++ b/core/src/themes/md/default.tokens.ts
@@ -46,6 +46,10 @@ export const defaultTheme: DefaultTheme = {
IonSpinner: {
size: 'medium',
},
+
+ IonText: {
+ hue: 'bold',
+ },
},
},
@@ -984,6 +988,26 @@ export const defaultTheme: DefaultTheme = {
},
},
+ IonText: {
+ hue: {
+ bold: {
+ semantic: {
+ default: {
+ color: currentColor('foreground'),
+ },
+ },
+ },
+
+ subtle: {
+ semantic: {
+ default: {
+ color: currentColor('foreground', { subtle: true }),
+ },
+ },
+ },
+ },
+ },
+
IonThumbnail: {
height: 'var(--ion-scaling-xxxl)',
width: 'var(--ion-scaling-xxxl)',
diff --git a/core/src/themes/themes.interfaces.ts b/core/src/themes/themes.interfaces.ts
index dec74cfc901..65c291ad847 100644
--- a/core/src/themes/themes.interfaces.ts
+++ b/core/src/themes/themes.interfaces.ts
@@ -4,6 +4,7 @@ import type { IonContentRecipe } from '../components/content/content.interfaces'
import type { IonItemDividerRecipe } from '../components/item-divider/item-divider.interfaces';
import type { IonProgressBarConfig, IonProgressBarRecipe } from '../components/progress-bar/progress-bar.interfaces';
import type { IonSpinnerConfig, IonSpinnerRecipe } from '../components/spinner/spinner.interfaces';
+import type { IonTextConfig, IonTextRecipe } from '../components/text/text.interfaces';
import type { IonThumbnailRecipe } from '../components/thumbnail/thumbnail.interfaces';
import type { IonicConfig as IonicGlobalConfig } from '../utils/config';
@@ -250,6 +251,7 @@ export type IonicConfig = IonicGlobalConfig & {
IonChip?: IonChipConfig;
IonProgressBar?: IonProgressBarConfig;
IonSpinner?: IonSpinnerConfig;
+ IonText?: IonTextConfig;
};
};
@@ -293,6 +295,7 @@ type Components = {
IonItemDivider?: IonItemDividerRecipe;
IonProgressBar?: IonProgressBarRecipe;
IonSpinner?: IonSpinnerRecipe;
+ IonText?: IonTextRecipe;
IonThumbnail?: IonThumbnailRecipe;
IonCard?: any;
@@ -320,3 +323,6 @@ export type NumberStringKeys = {
// Enforce keys are strings of numbers (like 50, '50', etc.)
[K in number as `${K}`]?: string;
};
+
+export const HUES = ['bold', 'subtle'] as const;
+export type Hue = (typeof HUES)[number];
diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts
index f53df1d8c8d..6bb4b88de6c 100644
--- a/packages/angular/src/directives/proxies.ts
+++ b/packages/angular/src/directives/proxies.ts
@@ -2436,14 +2436,14 @@ export declare interface IonTabButton extends Components.IonTabButton {}
@ProxyCmp({
- inputs: ['color', 'mode', 'theme']
+ inputs: ['color', 'hue', 'mode']
})
@Component({
selector: 'ion-text',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
- inputs: ['color', 'mode', 'theme'],
+ inputs: ['color', 'hue', 'mode'],
})
export class IonText {
protected el: HTMLIonTextElement;
diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts
index daf4149e3b6..e6b702f7e9a 100644
--- a/packages/angular/standalone/src/directives/proxies.ts
+++ b/packages/angular/standalone/src/directives/proxies.ts
@@ -2186,14 +2186,14 @@ export declare interface IonTabButton extends Components.IonTabButton {}
@ProxyCmp({
defineCustomElementFn: defineIonText,
- inputs: ['color', 'mode', 'theme']
+ inputs: ['color', 'hue', 'mode']
})
@Component({
selector: 'ion-text',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
- inputs: ['color', 'mode', 'theme'],
+ inputs: ['color', 'hue', 'mode'],
standalone: true
})
export class IonText {
diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts
index 55abafc7b73..f97c1f7b193 100644
--- a/packages/vue/src/proxies.ts
+++ b/packages/vue/src/proxies.ts
@@ -1051,7 +1051,8 @@ export const IonTab: StencilVueComponent = /*@__PURE__*/ defineConta
export const IonText: StencilVueComponent = /*@__PURE__*/ defineContainer('ion-text', defineIonText, [
- 'color'
+ 'color',
+ 'hue'
]);