Skip to content

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

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

brandyscarney wants to merge 20 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 7:45pm 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 thread core/src/components/checkbox/test/custom-icon/checkbox.e2e.ts Outdated
Comment thread core/src/components/checkbox/checkbox.tsx
Comment thread core/src/components/checkbox/checkbox.common.scss
Comment thread core/src/components/checkbox/checkbox.ionic.scss
Comment thread core/src/components/checkbox/checkbox.tsx Outdated
Comment thread core/src/components/checkbox/checkbox.tsx
Comment thread core/src/components/select-modal/select-modal.tsx Outdated
Comment thread core/src/components/checkbox/checkbox.native.scss Outdated
Comment thread core/src/components/checkbox/checkbox.common.scss Outdated
Comment thread core/api.txt

@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, the getter consolidation and the scss hoisting both look great.

One thing worries me and that's the main holdup, the rest is nits.

Comment on lines +456 to +457
<ion-icon class="checkbox-icon" icon={markIcon} part="icon" aria-hidden="true">
{!markIcon && <svg viewBox="0 0 24 24">{path}</svg>}

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 seems like a big issue, and it comes out of my flicker nit last round, so sorry for sending you down this path.

Gating the svg on !markIcon means a configured icon never gets replaced by the theme's mark. The ion-icon renders its <slot> only while its resolved svg content is empty, and ionicons never clears that content when icon goes away, so once it has loaded anything the slot is gone for good.

You don't need a programmatic change to hit it. Set only checkboxIndeterminateIcon, render an indeterminate checkbox, and one user click leaves the indeterminate icon painted on a now-checked box. With that config the icon renders in every state including checked, so the theme checkmark never appears at all.

Adding a key so the element gets replaced when the mark switches kind fixes it:

Suggested change
<ion-icon class="checkbox-icon" icon={markIcon} part="icon" aria-hidden="true">
{!markIcon && <svg viewBox="0 0 24 24">{path}</svg>}
<ion-icon
key={markIcon ? 'config-icon' : 'theme-mark'}
class="checkbox-icon"
icon={markIcon}
part="icon"
aria-hidden="true"
>
{!markIcon && <svg viewBox="0 0 24 24">{path}</svg>}
</ion-icon>

That holds up across all three config permutations, and with nothing configured the output is identical to what's here now, so the baselines aren't affected. Always rendering the svg and hiding it with CSS doesn't work, since the slot never comes back once it's gone.

Neither new test catches it, since newSpecPage doesn't hydrate ion-icon and the custom icon spec always sets both keys.

/**
* @prop --size: Size of the checkbox icon
*
* @prop --checkbox-background: Background of the checkbox 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.

One for the @prop --checkmark-width: Stroke width of the checkbox checkmark line just below, which didn't get the caveat @part mark did.

Setting it does nothing once an icon is set in the config, since it only applies through .checkbox-icon path and can't reach an icon rendering inside the ion-icon shadow root. With --checkmark-width: 10 the stroke comes out 10px without a config icon and 1px with one. The color prop is fine, since that one goes on the host and ionicons picks it up from currentColor.

The same clause you put on @part mark would cover it.

* @part label - The label text describing the checkbox.
* @part mark - The checkmark used to indicate the checked state.
* @part icon - The icon that displays the checked or indeterminate mark.
* @part mark - The checkmark used to indicate the checked state. Only applies when no icon is set in the config.

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
* @part mark - The checkmark used to indicate the checked state. Only applies when no icon is set in the config.
* @part mark - The mark used to indicate the checked or indeterminate state. Only applies when no icon is set in the config.

You widened the icon part to cover both states, and this one still says checked only, but part="mark" is on both branches of getSVGPath for every theme including the indeterminate paths. Someone styling the indeterminate dash would read the part table and think it doesn't apply. The config caveat is right.

* Otherwise, use the icon set in the config.
* If no icon is set in the config, use the default icon.
*/
get backButtonIcon() {

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 getter and eight others got the JSDoc treatment but kept a bare signature, so they infer any out of config.get instead of string. The guide section you added shows the annotated form and the other icon getters in core already have it.

There's one catch before you add them, though. Annotating this one breaks the build, since hasIconOnly just below returns this.backButtonIcon && !this.backButtonText and only type-checked while the getter was any. That means changing it to get hasIconOnly(): boolean with a !! on the first operand. The coercion changes no result, so it's two files rather than a one-liner. Up to you whether that belongs here or separately.

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

@ShaneK ShaneK Sep 18, 2026

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.

Yeah, I think this should go in the breaking changes doc, since it's a v9 part rather than the unreleased theme. Anything setting fill or stroke through it stops working, and ::part(container) > * now hits a different element.

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