Shadow parts in Ionic

January 14th 2022 Ionic 4+

When components were rewritten as web components in Ionic 4, many of them started using Shadow DOM to isolate their internal markup. This allowed the internal markup to be changed in newer versions without affecting the application code and markup. However, this also means that components can only be customized as much as the CSS custom properties provided allow.

In Ionic 5, many components exposed their internal markup as shadow parts, so it was again possible to fully customize it. This proved very useful when I reimplemented the date picker popup behavior in Ionic 6. When you put the date picker in a popup, it broke the default styling for invalid inputs on Android.

I used Angular reactive forms with validation:

ngOnInit(): void {
  this.form = new FormGroup({
    date: new FormControl(undefined, [Validators.required]),
    text: new FormControl(undefined, [Validators.required]),
  });
}

I placed the ion-input elements inside ion-item elements:

<form [formGroup]="form">
  <ion-item>
    <ion-label>Pick date</ion-label>
    <ion-input
      value="{{ form.controls['date'].value| date: 'dd.MM.yyyy' }}"
      id="date"
      readonly="true"
      class="ion-text-end"
    ></ion-input>
    <ion-popover trigger="date" size="cover">
      <ng-template>
        <ion-datetime
          presentation="date"
          formControlName="date"
          locale="sl-SI"
        ></ion-datetime>
      </ng-template>
    </ion-popover>
  </ion-item>
  <ion-item>
    <ion-label>Enter text</ion-label>
    <ion-input formControlName="text"></ion-input>
  </ion-item>
</form>

For the plain text inputs, this is enough to get a red underline rendered if the value is invalid. But for the date picker it does not work, because it is not placed directly inside the ion-item element:

Invalid date picker with incorrect styling

To fix this, I first tried adding code to manually apply the CSS classes for the red underline:

<ion-item
  [ngClass]="{'ion-touched': form.controls['date'].touched, 'ion-invalid': !form.controls['date'].valid}"
>
  <ion-label>Pick date</ion-label>
  <ion-input
    value="{{ form.controls['date'].value| date: 'dd.MM.yyyy' }}"
    id="date"
    readonly="true"
    class="ion-text-end"
  ></ion-input>
  <ion-popover trigger="date" size="cover">
    <ng-template>
      <ion-datetime
        presentation="date"
        formControlName="date"
        locale="sl-SI"
      ></ion-datetime>
    </ng-template>
  </ion-popover>
</ion-item>

Unfortunately, this did not work reliably because the ion-input element inside the ion-item interfered with the classes. So instead of adding the Ionic CSS classes, I added one of my own:

<ion-item
  [ngClass]="{'popover-invalid': form.controls['date'].touched && !form.controls['date'].valid}"
>
  <ion-label>Pick date</ion-label>
  <ion-input
    value="{{ form.controls['date'].value| date: 'dd.MM.yyyy' }}"
    id="date"
    readonly="true"
    class="ion-text-end"
  ></ion-input>
  <ion-popover trigger="date" size="cover">
    <ng-template>
      <ion-datetime
        presentation="date"
        formControlName="date"
        locale="sl-SI"
      ></ion-datetime>
    </ng-template>
  </ion-popover>
</ion-item>

There were no problems with this class. But now I had to add the styling for the red underline myself. This is where the shadow parts saved my day, because they allowed me to style the internal markup of ion-item that was responsible for the red underline:

:host-context(.md) ion-item.popover-invalid::part(native) {
  border-bottom-color: var(--highlight-color-invalid);
  border-bottom-width: 2px;
}

This CSS selector selects the shadow part named native with the ::part() selector and applies custom styling to it when it's inside my custom popover-invalid CSS class. It also ensures that this only happens on Android by using the ::host-context() selector.

This achieved the desired result:

Invalid date picker with correct styling

You can see the full source code for a working example in my GitHub repository.

I do not think there was much talk about shadow parts when they were added to components in Ionic 5, although they can be very useful when you need to customize the appearance of Ionic components and the available CSS custom properties are not enough. All shadow parts of a component are listed on its documentation page.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License