Skip to content

feat(config): add icon options and align ionic theme with md defaults - #31418

Open
brandyscarney wants to merge 19 commits into
nextfrom
FW-6998
Open

brandyscarney wants to merge 19 commits into
nextfrom
FW-6998

Conversation

@brandyscarney

@brandyscarney brandyscarney commented Sep 2, 2026

Copy link
Copy Markdown
Member

Issue number: internal


What is the current behavior?

Several components use Phosphor Icons by default only for the ionic theme.

What is the new behavior?

  • Removes the Phosphor Icon defaults for the ionic theme, instead replacing them with whatever icon md uses
  • This reverts most of the code in 08fc0b9
  • Phosphor Icon svgs can be set directly by any app consuming Ionic Framework using the config, or passed to ion-icon as a font.
  • Adds the missing config properties for icons which were removed:
    • Checkbox: checkboxCheckedIcon and checkboxIndeterminateIcon
    • Refresher: refresherArrowIcon
    • Select Modal: selectModalCancelIcon

Does this introduce a breaking change?

  • Yes
  • No

The ionic theme is unreleased so none of what is being removed was ever in the public API. This is for the next major version of Ionic.

Other information

Previews:

@vercel

vercel Bot commented Sep 2, 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 Sep 17, 2026 4:03pm UTC

Request Review

@brandyscarney brandyscarney changed the title refactor(many): remove Phosphor Icon defaults from the ionic theme feat(config): add icon options and align ionic theme with md defaults Sep 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The default icons have changed for the ionic theme, so all of these screenshot diffs are expected.

Comment on lines 99 to 122
.native-wrapper {
@include mixins.border-radius(var(--border-radius));

display: flex;

position: relative;

flex-shrink: 0;

align-items: center;

width: var(--size);
height: var(--size);

transition: var(--transition);

border-width: var(--border-width);
border-style: var(--border-style);
border-color: var(--border-color);

background: var(--checkbox-background);

box-sizing: border-box;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These styles were the same in the ionic and native files so I moved them to common.

--border-color: #{globals.$ion-primitives-neutral-800};
--checkmark-width: #{globals.$ion-scale-400};
--checkmark-height: var(--checkmark-width);
--checkmark-width: 3;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This matches md theme and makes it so all themes are consistently styled.

Comment on lines 82 to 101
.native-wrapper {
@include globals.border-radius(var(--border-radius));

flex-shrink: 0;

justify-content: center;

width: var(--size);
height: var(--size);

transition: var(--transition);

border-width: var(--border-width);
border-style: var(--border-style);
border-color: var(--border-color);

background: var(--checkbox-background);

box-sizing: border-box;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These styles were the same in the ionic and native files so I moved them to common.

>
{path}
</svg>
<div class="native-wrapper" part="container">

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I moved the container part to the element wrapping the icon rather than the svg so that the default checkmark and a custom configured icon can be styled consistently. I didn't notice any visual differences when comparing the styles in the docs demo, but this could be a breaking change for anyone targeting the icon specifically as an svg. If the reviewers agree, I can add this to the breaking changes document.

checkmark CSS properties. An icon set in the config replaces the
slotted path, so both are styled through the same element.
*/}
<ion-icon class="checkbox-icon" icon={markIcon} part="icon" aria-hidden="true">

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I decided to wrap the default checkmark svg in an ion-icon container so that developers can override the default or a custom configured icon the same way:

ion-checkbox::part(icon) {
  color: red;
}

Comment on lines -73 to -88
.checkbox-icon {
@include border-radius(var(--border-radius));

width: var(--size);
height: var(--size);

transition: var(--transition);

border-width: var(--border-width);
border-style: var(--border-style);
border-color: var(--border-color);

background: var(--checkbox-background);

box-sizing: border-box;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These styles were moved to the .native-wrapper, making them the same in the ionic and native files so I moved them to common.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The --checkmark-width now applies the same for all themes, making this have a custom stroke width.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The --size now applies the same for all themes, making this have a custom checkmark size.

@brandyscarney
brandyscarney marked this pull request as ready for review September 15, 2026 21:13
@brandyscarney
brandyscarney requested a review from a team as a code owner September 15, 2026 21:13
@brandyscarney
brandyscarney requested review from ShaneK and removed request for OS-jacobbell September 15, 2026 21:20

@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.

Really nice cleanup, great work! Two things I'd like to sort out before this goes in.

The new custom icon test reassigns window.Ionic, which drops the mode and theme that setContent injects, so all three of its matrix entries are rendering as md and the ios and ionic baselines aren't testing what they look like they're testing. Separately, ion-alert still has the Phosphor checkmark inlined in its ionic branch, so that's the one spot where the acceptance criteria aren't met yet.

Everything else is nits and a couple of coverage gaps, take or leave those.

Comment on lines +19 to +24
window.Ionic = {
config: {
checkboxCheckedIcon: '${phosphorHeart}',
checkboxIndeterminateIcon: 'star',
},
};

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
window.Ionic = {
config: {
checkboxCheckedIcon: '${phosphorHeart}',
checkboxIndeterminateIcon: 'star',
},
};
window.Ionic = window.Ionic || {};
window.Ionic.config = {
...window.Ionic.config,
checkboxCheckedIcon: '${phosphorHeart}',
checkboxIndeterminateIcon: 'star',
};

This seems like a big issue. The setContent helper is the only thing carrying mode and theme from configs(), and it does that by setting window.Ionic in the head, so reassigning the whole object here drops both and all three matrix entries render as md.

You can see it in the committed baselines, the ios and md ones are byte-identical for both tests in all three browsers. Merging instead gives ios 22px, md 18px and ionic 24px, matching a run with no custom config at all. Tests still pass. The snapshots will need regenerating though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You're right. I don't know how I didn't notice that the screenshots were all the same theme. 😂

Fixed: 550e889

);

if (theme === 'md') {
if (theme === 'md' || theme === 'ionic') {

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.

Over in ion-alert, this exact path is still inlined in its ionic branch, so a grep for phosphor doesn't find it. That leaves the alert checkbox drawing a filled Phosphor check on ionic while ion-checkbox draws the stroked md polyline, and checkboxCheckedIcon doesn't reach it. You can get there through ion-select with interface="alert" and multiple, or through alertController checkbox inputs.

Dropping the svg won't work on its own since ionic has no border-drawn fallback like ios and md do. Using the same md polyline and switching that stylesheet from fill to stroke at width 3 makes the alert mark match ion-checkbox exactly, with nothing showing when unchecked.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch! I was looking at the changes done on 08fc0b9 and searching the repository for Phosphor but this was done prior and not documented as being a Phosphor icon.

I changed the checkmark to be entirely done by CSS that is derived from the ion-checkbox so that those would appear identical. The downside is this won't scale with font-size on html (dynamic font scaling). However, that also didn't work before for the ionic theme. I think we should probably look into changing this to match the ion-checkbox implementation with an ion-icon and a wrapped svg. I added a question on the Jira ticket for this so we can discuss it internally.

Fix: 393a0bc

Let me know if you were thinking of a different implementation. I tried a few different ways with border-width and transform but none of them lined up.

opacity: 0;
fill: none;
stroke: currentColor;
stroke-width: var(--checkmark-width);

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.

This fill to stroke change is what makes the ionic indeterminate mark render at all, but nothing screenshots it. The indeterminate test doesn't opt into ionic-md, and the basic test page has no indeterminate checkbox in it. Adding the mode to that spec produces a good snapshot.

Probably a follow-up rather than something for this PR, but back-button, breadcrumb, fab-button and a few of the other components whose ionic default changed here have no ionic snapshots at all, so those swaps aren't verified either.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added: 8b271d6

I think getting all of the themes tested properly should be part of the modular ionic work, personally. Ideally it should be done at the time of migrating each component.

--border-color: #{globals.$ion-primitives-neutral-800};
--checkmark-width: #{globals.$ion-scale-400};
--checkmark-height: var(--checkmark-width);
--checkmark-width: 3;

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.

This changes what the property means on ionic rather than just its value. Setting --checkmark-width: 22px used to size the svg to 22x22 with a 1px stroke, now it gives you a 22px stroke width, and --checkmark-height is gone entirely. ios and md aren't affected since they already treated it as a stroke width.

I think that's worth a line in the PR body at least, since Mobile UI are the ones most likely to have set it. A stroke override through ::part(container) used to reach the mark on ionic too and no longer does.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I have added an info panel to the Jira ticket since this is unreleased code, letting Mobile UI know how to replace the removed/changed variables.

size,
} = this;
const theme = getIonTheme(this);
const markIcon = indeterminate ? this.checkboxIndeterminateIcon : this.checkboxCheckedIcon;

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.

Could you destructure these two into the block above rather than reading them off this? The four other components that got a config icon getter in this PR all do it that way.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

checkmark CSS properties. An icon set in the config replaces the
slotted path, so both are styled through the same element.
*/}
<ion-icon class="checkbox-icon" icon={markIcon} part="icon" aria-hidden="true">

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.

This is minor and I don't think it needs anything, but since the slotted svg is the fallback, a config icon that has to resolve asynchronously lets the theme's own mark paint first. With a base64 data URL the default check shows for a frame on ios and ionic, two on md, before swapping over. Only the first instance pays it because ionicons caches by URL, so it's small, but someone will report it as a flicker eventually.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I implemented a fix by only adding the svg when markIcon is undefined, that way there will be no flicker: badfb51

* Use the icon set in the config.
* If no icon is set in the config, use the default icon.
*/
private get cancelButtonIcon(): string {

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.

Nit: every other config icon getter is named after its config key, including the three you added here, so selectModalCancelIcon would match now that this one is config-backed. It's also the only rewritten getter missing the trailing "Return the configured ... icon or the default icon" comment. Up to you!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I actually was removing the single lines above the return because the comment on top of the getter already explains that. I went through and removed the rest of them but let me know if you want them all added back: 861c01c

stroke: var(--checkmark-color);
stroke-width: var(--checkmark-width);
.checkbox-icon {
font-size: calc(var(--size) - 2 * var(--border-width));

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.

The --border-width prop accepts thin, medium and thick, and those make this calc() invalid so the font-size falls back to the inherited 16px. With thick the mark goes non-square, ios comes out 12x16 and md 8x16, where before this PR they stayed 22x22 and 18x18. I tried the obvious flex-based replacement and it doesn't fix the keyword case and makes the default one worse, so I don't have a fix to offer, just flagging it in case you think it's worth handling.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It looks like I could remove that entirely: 73ce038

* checkmark icon and any configured icon apply color consistently.
*/
.checkbox-icon {
position: relative;

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
position: relative;

I don't think anything positions against this any more. None of the four checkbox stylesheets absolutely position a descendant of the icon, and the real positioning moved onto the wrapper in this PR.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread core/api.txt
ion-checkbox,part,container
ion-checkbox,part,error-text
ion-checkbox,part,helper-text
ion-checkbox,part,icon

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.

The @prop doc for --checkbox-background probably wants to move into the common stylesheet alongside it, otherwise this file keeps listing it for ios and md only. The split predates the PR, but you already moved --checkmark-width's doc across as part of the same consolidation. Up to you!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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