diff --git a/BREAKING.md b/BREAKING.md index b52dcfa2f20..a2ec3ec4e9d 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -491,7 +491,8 @@ The following breaking changes apply to `ion-router-link`: 2. `--background` has been removed. [2](#version-10x-router-link-removed-css-variables) 3. The color applied by the `color` prop is now driven by the centralized Ionic Theming system, scoped to the new `hue` property. [3](#version-10x-router-link-hue) 4. The text color now uses the color's `foreground` variant instead of `base`. [4](#version-10x-router-link-foreground) -5. Mode classes (`ion-router-link.md`, `ion-router-link.ios`) are no longer supported. [5](#version-10x-router-link-mode-classes) +5. In the `md` and `ios` themes, a focused router link now shows a tinted background instead of the browser's focus ring. In the `ionic` theme, it shows a 2px ring. [5](#version-10x-router-link-focus) +6. Mode classes (`ion-router-link.md`, `ion-router-link.ios`) are no longer supported. [6](#version-10x-router-link-mode-classes) @@ -528,6 +529,20 @@ A bold router link now uses the `foreground` variant of its color instead of `ba | Default | `IonRouterLink.hue.bold.default.color` | `var(--ion-color-primary)` | `--ion-router-link-hue-bold-default-color` | | `color` set | `IonRouterLink.hue.bold.semantic.default.color` | `var(--ion-color-base)` | `--ion-router-link-hue-bold-semantic-default-color` | + + +When a router link is focused with the keyboard in the `md` and `ios` themes, it now shows a background tinted with its color instead of the browser's default focus ring, the same as a clear button: 12% opacity in `md` and 10% in `ios`. The background reaches 2px above and below the text and 4px to each side, without moving it. In the `ionic` theme, it shows a 2px ring with no background. Customize it with the tokens for global styles, or the CSS variables for component-specific styles: + +| Property | Token (global) | CSS variable (component-specific) | +|---|---|---| +| Background | `IonRouterLink.hue.bold.focus.background` | `--ion-router-link-hue-bold-focus-background` | +| Background, `color` set | `IonRouterLink.hue.bold.semantic.focus.background` | `--ion-router-link-hue-bold-semantic-focus-background` | +| Corner radius | `IonRouterLink.state.focus.border.radius` | `--ion-router-link-state-focus-border-radius` | +| Padding | `IonRouterLink.state.focus.padding` | `--ion-router-link-state-focus-padding-top`, `-end`, `-bottom`, `-start` | + +> [!NOTE] +> A router link with `hue="subtle"` reads the `subtle` tokens instead. To show a ring, set `IonRouterLink.state.focus.ring.color`, `.style` and `.width`. + Remove any instances that target the mode classes: `ion-router-link.md`, `ion-router-link.ios`. diff --git a/core/api.txt b/core/api.txt index 1974b25afc9..c5db473b61e 100644 --- a/core/api.txt +++ b/core/api.txt @@ -2241,10 +2241,34 @@ ion-router-link,prop,rel,string | undefined,undefined,false,false ion-router-link,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-router-link,prop,routerDirection,"back" | "forward" | "root",'forward',false,false ion-router-link,prop,target,string | undefined,undefined,false,false +ion-router-link,css-prop,--ion-router-link-hue-bold-activated-color ion-router-link,css-prop,--ion-router-link-hue-bold-default-color +ion-router-link,css-prop,--ion-router-link-hue-bold-focus-background +ion-router-link,css-prop,--ion-router-link-hue-bold-semantic-activated-color ion-router-link,css-prop,--ion-router-link-hue-bold-semantic-default-color +ion-router-link,css-prop,--ion-router-link-hue-bold-semantic-focus-background +ion-router-link,css-prop,--ion-router-link-hue-bold-semantic-visited-color +ion-router-link,css-prop,--ion-router-link-hue-bold-visited-color +ion-router-link,css-prop,--ion-router-link-hue-subtle-activated-color ion-router-link,css-prop,--ion-router-link-hue-subtle-default-color +ion-router-link,css-prop,--ion-router-link-hue-subtle-focus-background +ion-router-link,css-prop,--ion-router-link-hue-subtle-semantic-activated-color ion-router-link,css-prop,--ion-router-link-hue-subtle-semantic-default-color +ion-router-link,css-prop,--ion-router-link-hue-subtle-semantic-focus-background +ion-router-link,css-prop,--ion-router-link-hue-subtle-semantic-visited-color +ion-router-link,css-prop,--ion-router-link-hue-subtle-visited-color +ion-router-link,css-prop,--ion-router-link-state-activated-text-decoration +ion-router-link,css-prop,--ion-router-link-state-focus-border-radius +ion-router-link,css-prop,--ion-router-link-state-focus-padding-bottom +ion-router-link,css-prop,--ion-router-link-state-focus-padding-end +ion-router-link,css-prop,--ion-router-link-state-focus-padding-start +ion-router-link,css-prop,--ion-router-link-state-focus-padding-top +ion-router-link,css-prop,--ion-router-link-state-focus-ring-color +ion-router-link,css-prop,--ion-router-link-state-focus-ring-offset +ion-router-link,css-prop,--ion-router-link-state-focus-ring-style +ion-router-link,css-prop,--ion-router-link-state-focus-ring-width +ion-router-link,css-prop,--ion-router-link-state-focus-text-decoration +ion-router-link,css-prop,--ion-router-link-text-decoration ion-router-outlet,shadow ion-router-outlet,prop,animated,boolean,true,false,false diff --git a/core/src/components/router-link/_router-link.docs.scss b/core/src/components/router-link/_router-link.docs.scss index 90f10431935..de29ff7dd42 100644 --- a/core/src/components/router-link/_router-link.docs.scss +++ b/core/src/components/router-link/_router-link.docs.scss @@ -1,6 +1,30 @@ /** * @prop --ion-router-link-hue-bold-default-color: Text color of the `bold` hue when no semantic color is applied + * @prop --ion-router-link-hue-bold-visited-color: Text color of the `bold` hue when no semantic color is applied and the link has been visited + * @prop --ion-router-link-hue-bold-activated-color: Text color of the `bold` hue when no semantic color is applied and the link is pressed + * @prop --ion-router-link-hue-bold-focus-background: Background of the `bold` hue when no semantic color is applied and the link is focused * @prop --ion-router-link-hue-bold-semantic-default-color: Text color of the `bold` hue when a semantic color is applied + * @prop --ion-router-link-hue-bold-semantic-visited-color: Text color of the `bold` hue when a semantic color is applied and the link has been visited + * @prop --ion-router-link-hue-bold-semantic-activated-color: Text color of the `bold` hue when a semantic color is applied and the link is pressed + * @prop --ion-router-link-hue-bold-semantic-focus-background: Background of the `bold` hue when a semantic color is applied and the link is focused * @prop --ion-router-link-hue-subtle-default-color: Text color of the `subtle` hue when no semantic color is applied + * @prop --ion-router-link-hue-subtle-visited-color: Text color of the `subtle` hue when no semantic color is applied and the link has been visited + * @prop --ion-router-link-hue-subtle-activated-color: Text color of the `subtle` hue when no semantic color is applied and the link is pressed + * @prop --ion-router-link-hue-subtle-focus-background: Background of the `subtle` hue when no semantic color is applied and the link is focused * @prop --ion-router-link-hue-subtle-semantic-default-color: Text color of the `subtle` hue when a semantic color is applied + * @prop --ion-router-link-hue-subtle-semantic-visited-color: Text color of the `subtle` hue when a semantic color is applied and the link has been visited + * @prop --ion-router-link-hue-subtle-semantic-activated-color: Text color of the `subtle` hue when a semantic color is applied and the link is pressed + * @prop --ion-router-link-hue-subtle-semantic-focus-background: Background of the `subtle` hue when a semantic color is applied and the link is focused + * @prop --ion-router-link-state-activated-text-decoration: Text decoration of the router link when it is pressed + * @prop --ion-router-link-state-focus-border-radius: Corner radius of the focus ring and background + * @prop --ion-router-link-state-focus-padding-bottom: Bottom padding of the focus ring and background + * @prop --ion-router-link-state-focus-padding-end: End padding of the focus ring and background + * @prop --ion-router-link-state-focus-padding-start: Start padding of the focus ring and background + * @prop --ion-router-link-state-focus-padding-top: Top padding of the focus ring and background + * @prop --ion-router-link-state-focus-ring-color: Color of the focus ring + * @prop --ion-router-link-state-focus-ring-offset: Space between the focus ring and the router link + * @prop --ion-router-link-state-focus-ring-style: Style of the focus ring + * @prop --ion-router-link-state-focus-ring-width: Width of the focus ring + * @prop --ion-router-link-state-focus-text-decoration: Text decoration of the router link when it is focused + * @prop --ion-router-link-text-decoration: Text decoration of the router link, such as an underline */ diff --git a/core/src/components/router-link/router-link.interface.ts b/core/src/components/router-link/router-link.interface.ts index 37197058a75..4f66d770d6a 100644 --- a/core/src/components/router-link/router-link.interface.ts +++ b/core/src/components/router-link/router-link.interface.ts @@ -1,19 +1,57 @@ -import type { Hue } from '../../themes/themes.interfaces'; +import type { Hue, IonPadding } from '../../themes/themes.interfaces'; + +type IonRouterLinkStateDefinition = { + background?: string; + color?: string; +}; export type IonRouterLinkRecipe = { hue?: { [K in Hue]?: { - default?: { - color?: string; - }; + activated?: IonRouterLinkStateDefinition; + default?: IonRouterLinkStateDefinition; + focus?: IonRouterLinkStateDefinition; + visited?: IonRouterLinkStateDefinition; /** Any of the semantic colors like primary, secondary, etc. */ semantic?: { - default?: { - color?: string; - }; + activated?: IonRouterLinkStateDefinition; + default?: IonRouterLinkStateDefinition; + focus?: IonRouterLinkStateDefinition; + visited?: IonRouterLinkStateDefinition; + }; + }; + }; + + state?: { + activated?: { + text?: { + decoration?: string; }; }; + + focus?: { + border?: { + radius?: string; + }; + + padding?: IonPadding; + + ring?: { + color?: string; + offset?: string; + style?: string; + width?: string; + }; + + text?: { + decoration?: string; + }; + }; + }; + + text?: { + decoration?: string; }; }; diff --git a/core/src/components/router-link/router-link.scss b/core/src/components/router-link/router-link.scss index 02962cd2b70..01f16ca3849 100644 --- a/core/src/components/router-link/router-link.scss +++ b/core/src/components/router-link/router-link.scss @@ -4,25 +4,106 @@ // Router Link // -------------------------------------------------- +:host { + text-decoration: var(--ion-router-link-text-decoration); +} + a { @include mixins.text-inherit(); + + // The theme's focus ring or background below replaces the browser's own ring + outline: none; } // Router Link Hues // -------------------------------------------------- -:host(.router-link-hue-bold) { - color: var(--ion-router-link-hue-bold-default-color); +/** + * States are written in this order: default -> visited -> activated. + * The rules share a specificity, so a later state overrides an earlier + * one: activated overrides visited. A state a theme doesn't set falls + * back to the default color. + */ +@each $hue in (bold, subtle) { + :host(.router-link-hue-#{$hue}) { + color: var(--ion-router-link-hue-#{$hue}-default-color); + } + + :host(.router-link-hue-#{$hue}) a:visited { + color: var(--ion-router-link-hue-#{$hue}-visited-color, var(--ion-router-link-hue-#{$hue}-default-color)); + } + + :host(.router-link-hue-#{$hue}.ion-activated) a { + color: var(--ion-router-link-hue-#{$hue}-activated-color, var(--ion-router-link-hue-#{$hue}-default-color)); + } + + :host(.router-link-hue-#{$hue}.ion-color) { + color: var(--ion-router-link-hue-#{$hue}-semantic-default-color); + } + + :host(.router-link-hue-#{$hue}.ion-color) a:visited { + color: var( + --ion-router-link-hue-#{$hue}-semantic-visited-color, + var(--ion-router-link-hue-#{$hue}-semantic-default-color) + ); + } + + :host(.router-link-hue-#{$hue}.ion-color.ion-activated) a { + color: var( + --ion-router-link-hue-#{$hue}-semantic-activated-color, + var(--ion-router-link-hue-#{$hue}-semantic-default-color) + ); + } +} + +// Router Link States +// -------------------------------------------------- + +/** + * States are written in this order: focus -> activated. + * The rules share a specificity, so activated overrides focus. + * A state a theme doesn't set falls back to the default decoration. + */ +:host(.ion-focused) { + text-decoration: var(--ion-router-link-state-focus-text-decoration, var(--ion-router-link-text-decoration)); } -:host(.router-link-hue-subtle) { - color: var(--ion-router-link-hue-subtle-default-color); +:host(.ion-activated) { + text-decoration: var(--ion-router-link-state-activated-text-decoration, var(--ion-router-link-text-decoration)); } -:host(.router-link-hue-bold.ion-color) { - color: var(--ion-router-link-hue-bold-semantic-default-color); +/** + * A theme shows focus with a ring, a background or both. A part + * the theme doesn't set is left out. + */ +:host(.ion-focused) a { + @include mixins.border-radius(var(--ion-router-link-state-focus-border-radius)); + @include mixins.padding( + var(--ion-router-link-state-focus-padding-top), + var(--ion-router-link-state-focus-padding-end), + var(--ion-router-link-state-focus-padding-bottom), + var(--ion-router-link-state-focus-padding-start) + ); + + // Cancels the side padding, so the text doesn't move on focus + @include mixins.margin-horizontal( + calc(-1 * var(--ion-router-link-state-focus-padding-start)), + calc(-1 * var(--ion-router-link-state-focus-padding-end)) + ); + @include mixins.focused-state( + var(--ion-router-link-state-focus-ring-width), + var(--ion-router-link-state-focus-ring-style), + var(--ion-router-link-state-focus-ring-color), + $offset: var(--ion-router-link-state-focus-ring-offset) + ); } -:host(.router-link-hue-subtle.ion-color) { - color: var(--ion-router-link-hue-subtle-semantic-default-color); +@each $hue in (bold, subtle) { + :host(.router-link-hue-#{$hue}.ion-focused) a { + background: var(--ion-router-link-hue-#{$hue}-focus-background); + } + + :host(.router-link-hue-#{$hue}.ion-color.ion-focused) a { + background: var(--ion-router-link-hue-#{$hue}-semantic-focus-background); + } } diff --git a/core/src/components/router-link/router-link.tsx b/core/src/components/router-link/router-link.tsx index a14aa82bdb5..1745f3a8412 100644 --- a/core/src/components/router-link/router-link.tsx +++ b/core/src/components/router-link/router-link.tsx @@ -109,6 +109,7 @@ export class RouterLink implements ComponentInterface { class={createColorClasses(color, { [`router-link-hue-${hueValue}`]: true, 'ion-activatable': true, + 'ion-focusable': true, })} > diff --git a/core/src/components/router-link/test/router-link.spec.ts b/core/src/components/router-link/test/router-link.spec.ts index cf4f7d1a4a9..a2d976f4fce 100644 --- a/core/src/components/router-link/test/router-link.spec.ts +++ b/core/src/components/router-link/test/router-link.spec.ts @@ -77,6 +77,15 @@ describe('router-link', () => { expect(page.root).toHaveClass('ion-activatable'); }); + it('should add the ion-focusable class', async () => { + const page = await newSpecPage({ + components: [RouterLink], + html: `Link`, + }); + + expect(page.root).toHaveClass('ion-focusable'); + }); + describe('click', () => { const setup = async (href: string) => { const page = await newSpecPage({ diff --git a/core/src/components/router-link/test/states/index.html b/core/src/components/router-link/test/states/index.html new file mode 100644 index 00000000000..5c28abb2de7 --- /dev/null +++ b/core/src/components/router-link/test/states/index.html @@ -0,0 +1,62 @@ + + + + + Router Link - States + + + + + + + + + + + + + + + Router Link - States + + + + +

Focused

+ +

+ Default + Danger + Subtle + Subtle danger +

+ +

Activated

+ +

+ Default + Danger + Subtle + Subtle danger +

+ +

Visited

+ + +

+ Default + Danger + Subtle + Subtle danger +

+
+
+ + diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts b/core/src/components/router-link/test/states/router-link.e2e.ts new file mode 100644 index 00000000000..b507f411916 --- /dev/null +++ b/core/src/components/router-link/test/states/router-link.e2e.ts @@ -0,0 +1,67 @@ +import { expect } from '@playwright/test'; +import { configs, test } from '@utils/test/playwright'; + +/** + * This behavior does not vary across directions. + */ +configs({ modes: ['ios', 'md', 'ionic-md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('router-link: states'), () => { + test('should not have visual regressions', async ({ page }) => { + /** + * Firefox's test browser keeps no history, so its visited + * links show the default color. + */ + await page.setContent( + ` + + +
+
+ Default + Danger + Subtle + Subtle danger +
+ +
+ Default + Danger + Subtle + Subtle danger +
+ +
+ Default + Danger + Subtle + Subtle danger +
+
+ `, + config + ); + + const container = page.locator('.container'); + + await expect(container).toHaveScreenshot(screenshot(`router-link-states`)); + }); + }); +}); diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Chrome-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..5bb77a506c7 Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Chrome-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Firefox-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..0b61eb607c0 Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Firefox-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Safari-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Safari-linux.png new file mode 100644 index 00000000000..a5481e13cbc Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ionic-md-ltr-light-Mobile-Safari-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..e4a4c6e846e Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..a09707c0f4a Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Safari-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..ed4b532a075 Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Chrome-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..73756b56bfd Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Firefox-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..962a67d228c Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Safari-linux.png b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..fcfc9c7d9fd Binary files /dev/null and b/core/src/components/router-link/test/states/router-link.e2e.ts-snapshots/router-link-states-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/themes/ionic/default.tokens.ts b/core/src/themes/ionic/default.tokens.ts index 85e39f3eee6..f9865f28cfd 100644 --- a/core/src/themes/ionic/default.tokens.ts +++ b/core/src/themes/ionic/default.tokens.ts @@ -925,10 +925,18 @@ export const defaultTheme: DefaultTheme = { IonRouterLink: { hue: { bold: { + activated: { + color: '#09358a', + }, + default: { color: ionColor('primary', 'foreground'), }, + visited: { + color: '#09358a', + }, + semantic: { default: { color: currentColor('foreground'), @@ -937,10 +945,18 @@ export const defaultTheme: DefaultTheme = { }, subtle: { + activated: { + color: '#09358a', + }, + default: { color: ionColor('primary', 'foreground', { subtle: true }), }, + visited: { + color: '#09358a', + }, + semantic: { default: { color: currentColor('foreground', { subtle: true }), @@ -948,6 +964,35 @@ export const defaultTheme: DefaultTheme = { }, }, }, + + state: { + activated: { + text: { + decoration: 'underline', + }, + }, + + focus: { + border: { + radius: 'var(--ion-radii-sm)', + }, + + // The ring hugs the text at the sides and its line box above and below + padding: { + top: 'var(--ion-spacing-xxxs)', + end: 'var(--ion-spacing-0)', + bottom: 'var(--ion-spacing-xxxs)', + start: 'var(--ion-spacing-0)', + }, + + ring: { + color: '#b5c0f7', + offset: 'var(--ion-spacing-0)', + style: 'solid', + width: 'var(--ion-border-width-xxs)', + }, + }, + }, }, IonRow: { diff --git a/core/src/themes/ios/default.tokens.ts b/core/src/themes/ios/default.tokens.ts index a3fb9bf1fb5..d86d7acfc60 100644 --- a/core/src/themes/ios/default.tokens.ts +++ b/core/src/themes/ios/default.tokens.ts @@ -961,10 +961,18 @@ export const defaultTheme: DefaultTheme = { color: ionColor('primary', 'foreground'), }, + focus: { + background: ionColor('primary', 'base', { alpha: 0.1 }), + }, + semantic: { default: { color: currentColor('foreground'), }, + + focus: { + background: currentColor('base', { alpha: 0.1 }), + }, }, }, @@ -973,10 +981,33 @@ export const defaultTheme: DefaultTheme = { color: ionColor('primary', 'foreground', { subtle: true }), }, + focus: { + background: ionColor('primary', 'base', { alpha: 0.1 }), + }, + semantic: { default: { color: currentColor('foreground', { subtle: true }), }, + + focus: { + background: currentColor('base', { alpha: 0.1 }), + }, + }, + }, + }, + + state: { + focus: { + border: { + radius: 'var(--ion-radii-xxs)', + }, + + padding: { + top: 'var(--ion-spacing-xxxs)', + end: 'var(--ion-spacing-xxs)', + bottom: 'var(--ion-spacing-xxxs)', + start: 'var(--ion-spacing-xxs)', }, }, }, diff --git a/core/src/themes/md/default.tokens.ts b/core/src/themes/md/default.tokens.ts index d39991aea7e..90b1861adb8 100644 --- a/core/src/themes/md/default.tokens.ts +++ b/core/src/themes/md/default.tokens.ts @@ -1077,10 +1077,18 @@ export const defaultTheme: DefaultTheme = { color: ionColor('primary', 'foreground'), }, + focus: { + background: ionColor('primary', 'base', { alpha: 0.12 }), + }, + semantic: { default: { color: currentColor('foreground'), }, + + focus: { + background: currentColor('base', { alpha: 0.12 }), + }, }, }, @@ -1089,10 +1097,33 @@ export const defaultTheme: DefaultTheme = { color: ionColor('primary', 'foreground', { subtle: true }), }, + focus: { + background: ionColor('primary', 'base', { alpha: 0.12 }), + }, + semantic: { default: { color: currentColor('foreground', { subtle: true }), }, + + focus: { + background: currentColor('base', { alpha: 0.12 }), + }, + }, + }, + }, + + state: { + focus: { + border: { + radius: 'var(--ion-radii-sm)', + }, + + padding: { + top: 'var(--ion-spacing-xxxs)', + end: 'var(--ion-spacing-xxs)', + bottom: 'var(--ion-spacing-xxxs)', + start: 'var(--ion-spacing-xxs)', }, }, }, diff --git a/core/src/themes/mixins.scss b/core/src/themes/mixins.scss index 7c64964f108..1406e3d37ed 100644 --- a/core/src/themes/mixins.scss +++ b/core/src/themes/mixins.scss @@ -617,6 +617,8 @@ $rtl-use-host-context: true !default; } // Mixin that applies focus styles to interactive elements. +// `$offset` sets the space between the ring and the element. Without +// it, `$addOffset` offsets the ring by its own width. // // Example: // @@ -626,13 +628,15 @@ $rtl-use-host-context: true !default; // } // ``` // -------------------------------------------------- -@mixin focused-state($width: null, $style: solid, $color: null, $addOffset: true) { +@mixin focused-state($width: null, $style: solid, $color: null, $addOffset: true, $offset: null) { @if $width == null or $color == null { outline: none; } @else { outline: $width $style $color; - @if $addOffset { + @if $offset != null { + outline-offset: $offset; + } @else if $addOffset { outline-offset: $width; } }