Skip to content

feat(router-link): add recipe and tokens - #31705

Merged
thetaPC merged 7 commits into
FW-6903-testsfrom
FW-6903
Nov 12, 2026
Merged

thetaPC merged 7 commits into
FW-6903-testsfrom
FW-6903

Conversation

@thetaPC

@thetaPC thetaPC commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Issue number: resolves internal


What is the current behavior?

ion-router-link does not fragment styles based on themes.

What is the new behavior?

  • Migrates ion-router-link to Modular Ionic, with IonRouterLinkRecipe, IonRouterLinkConfig and per-theme tokens
  • Adds a hue property. subtle is new, so it has no next reference
  • Bold links now use the color's foreground variant instead of base, so they're darker and easier to read on the page. This applies to the default link and a link with color set
  • Moves the color examples from the basic test page to a new hue test page

Does this introduce a breaking change?

  • Yes
  • No

The router link's CSS variables are replaced by token-driven custom properties. Use the global token for theme-wide styling, or the CSS variable for component-specific overrides.

  1. Replaced CSS variables
Old Token (global) CSS variable (component-specific)
--color IonRouterLink.hue.bold.default.color --ion-router-link-hue-bold-default-color
  1. Removed CSS variable: --background is removed, since a router link has no background of its own. Set background on ion-router-link directly instead.

  2. Text color: bold links use foreground instead of base. To revert:

Link Token (global) Value CSS variable (component-specific)
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
  1. Mode classes: remove any selectors that target ion-router-link.md or ion-router-link.ios.

Other information

Known gaps

  • In the ionic theme, links are a different blue, red and dark than on next. That was already true before this PR: ionic-modular's ionic palette uses different values for those colors
  • In the light palette, subtle and bold look the same, because both use the same foreground value. That comes from the palette (FW-6417), and ion-text looks the same way
  • With no theme loaded (ionic:theme=base), a router link renders as plain text in the inherited text color with no underline, since the base theme has no palette for its tokens. It's still readable and clickable, and ion-text behaves the same way

How to test

Check Before After What to look for
Default link, md Before After "Router Link" is a slightly darker blue in After. The custom magenta link is unchanged
Default link, ios dark Before After The link is a slightly darker blue in After
Check Preview What to look for
Hues, ionic Preview Every color is readable in both sections. Tap Toggle Color and the last link switches between tertiary and secondary

@thetaPC
thetaPC added this pull request to stack #31706 November 6, 2026 00:12
@vercel

vercel Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
ionic-framework Ready Ready Preview Nov 12, 2026 11:26pm UTC

Request Review

@github-actions github-actions Bot added the package: core @ionic/core package label Nov 6, 2026
<p>
<ion-router-link href="#" download="yes" class="custom">Custom Router Link</ion-router-link>
</p>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These were simply moved to the hue page.

@thetaPC
thetaPC marked this pull request as ready for review November 6, 2026 04:07
@thetaPC
thetaPC requested a review from a team as a code owner November 6, 2026 04:07
@thetaPC
thetaPC requested a review from ShaneK November 6, 2026 04:08

@brandyscarney brandyscarney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! My questions are non-blocking unless you decide to change the implementation.

Comment thread BREAKING.md Resolved
Comment thread BREAKING.md Resolved

@brandyscarney brandyscarney left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Thanks for making the updates.

@ShaneK ShaneK left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Just a few small things on the tests and docs.

});
});
});
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
});
});
/**
* The bold and subtle hues resolve to the same color in the default
* light palette, so the screenshots above can't tell them apart.
* Each hue is given its own color here to verify it reads its own tokens.
*/
configs({ modes: ['ionic-md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('router-link: hue tokens'), () => {
HUES.forEach((hue) => {
test(`should use the ${hue} hue tokens`, async ({ page }) => {
await page.setContent(
`
<ion-router-link
id="default"
href="#"
hue="${hue}"
style="--ion-router-link-hue-${hue}-default-color: rgb(1, 2, 3)"
>Default</ion-router-link>
<ion-router-link
id="danger"
href="#"
hue="${hue}"
color="danger"
style="--ion-router-link-hue-${hue}-semantic-default-color: rgb(4, 5, 6)"
>Danger</ion-router-link>
`,
config
);
await expect(page.locator('#default')).toHaveCSS('color', 'rgb(1, 2, 3)');
await expect(page.locator('#danger')).toHaveCSS('color', 'rgb(4, 5, 6)');
});
});
});
});

All 9 subtle screenshots are the same file as their bold twins, so if the subtle rules pointed at the bold tokens by mistake, the whole hue suite would still pass (I tried it). Giving each hue its own color like this fails right away in that case, and it doesn't add any screenshots. I know the matching colors are the palette thing from Brandy's thread, I'm only worried about the test not being able to tell the hues apart.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 946b1ab, thanks!

Comment thread BREAKING.md Outdated

<h5 id="version-10x-router-link-foreground">Text color</h5>

A bold router link now uses the `foreground` variant of its color instead of `base`, in every theme. `foreground` is darker, so the text stays readable on the page. This applies to the default link, which uses `primary`, and to a link with `color` set. To revert to the previous appearance, set the tokens for global styles or the CSS variables for component-specific styles:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A bold router link now uses the `foreground` variant of its color instead of `base`, in every theme. `foreground` is darker, so the text stays readable on the page. This applies to the default link, which uses `primary`, and to a link with `color` set. To revert to the previous appearance, set the tokens for global styles or the CSS variables for component-specific styles:
A bold router link now uses the `foreground` variant of its color instead of `base`, in every theme. In the light palette, `foreground` is a darker shade of the color, so the text stays readable on the page. This applies to the default link, which uses `primary`, and to a link with `color` set. To revert to the previous appearance, set the tokens for global styles or the CSS variables for component-specific styles:

I know the dark palette values are being sorted out in the cleanup from Brandy's thread, so this is only about the wording. In both high-contrast palettes foreground is the same as base, so the "darker" part is only true in light. Up to you!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in abfff52, thanks!

directions: ['ltr'],
palettes: ['light', 'dark', 'high-contrast', 'high-contrast-dark'],
}).forEach(({ title, config }) => {
test.describe(title('router-link: hue a11y'), () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the old router-link: a11y block is redundant now? A link with no hue resolves to bold, and this block already runs the same markup with hue="bold" across every palette the old one covers, so I'd delete the old one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, every theme defaults to bold. Removed in 84ba52e.

# Conflicts:
#	core/src/components/router-link/router-link.tsx
@thetaPC
thetaPC merged commit 3112719 into ionic-modular Nov 12, 2026
54 checks passed
@thetaPC
thetaPC deleted the FW-6903 branch November 12, 2026 23:40

This branch was successfully deployed

1 active deployment
Preview — 1e580e20 Deployed Nov 12, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core @ionic/core package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants