Customize your Ionic Framework app with CSS Shadow Parts!
Tags: css, webcomponents, ionic, webdev
Published: 2020-08-21 15:20:36

---
title: Customize your Ionic Framework app with CSS Shadow Parts!
published: true
date: 2020-07-15 17:16:55 UTC
tags: CSS,webcomponents,ionic, webdev
canonical_url: https://ionicframework.com/blog/customize-your-ionic-framework-app-with-css-shadow-parts/
---

<p style="font-size:25px; line-height: 150%;">Official support for <a href="https://www.w3.org/TR/css-shadow-parts-1/" target="_blank" rel="noopener noreferrer">CSS Shadow Parts</a> in Ionic Framework has landed! Shadow parts make it easier to theme and customize Ionic Framework components.</p>

<p style="font-size:25px; line-height: 150%;">They replace the need for a large amount of CSS variables by adding the ability to target any CSS property on an element inside of a <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM" target="_blank" rel="noopener noreferrer">shadow tree</a>.</p>

## Why Shadow Parts? 🤔

To better understand what shadow parts are, we need to understand why we need them. The <a href="https://ionicframework.com/blog/introducing-ionic-4-ionic-for-everyone/" target="_blank" rel="noopener noreferrer">release of Ionic Framework 4</a> at the beginning of last year migrated all of our components to become a distributed set of <a href="https://www.webcomponents.org/introduction" target="_blank" rel="noopener noreferrer">Web Components</a>. Web Components follow the <a href="https://w3c.github.io/webcomponents/spec/shadow/" target="_blank" rel="noopener noreferrer">Shadow DOM specification</a> in order to encapsulate styles and markup. 

Shadow DOM is useful for preventing styles from leaking out of components and unintentionally applying to other elements. For example, we assign a `.button` class to our `ion-button` component. If an Ionic Framework user were to set the class `.button` on one of their own elements, it would inherit the Ionic button styles in past versions of the framework. Since `ion-button` is now a Shadow Web Component, this is no longer a problem. 

<!--more-->

However, due to this encapsulation, styles aren’t able to bleed into inner elements of a Shadow component either. This means that if a Shadow component renders elements inside of its shadow tree, a user isn’t able to target the inner element with their CSS. Using the `ion-select` component as an example, it renders the following markup:

```html
<ion-select>
  #shadow-root
    <div class=”select-text select-placeholder”></div>
    <div class=”select-icon”></div>
</ion-select>
```

The placeholder text and icon elements are inside of the `#shadow-root`, which means the following CSS will **NOT** work to style the placeholder:

```css
/* Does NOT work */
ion-select .select-placeholder {
  color: blue;
}
```

<a href="https://developer.mozilla.org/en-US/docs/Web/CSS/--*" target="_blank" rel="noopener noreferrer">CSS custom properties (variables)</a> have been useful in solving this problem. We’ve created CSS variables to target many of the CSS properties on these inner elements. In this case, the placeholder color could be changed using:

```css
/* DOES work */
ion-select {
  --placeholder-color: blue;
}
```

The problem with this approach is that if we were to declare every possible property as a custom property it would cause bloat in the CSS files and slow performance. Instead, we have relied on style inheritance where possible and implemented custom properties based on commonly customized components and user requests. This has led us to discover that there are still many challenges with customizing our components. 

This is where <a href="https://www.w3.org/TR/css-shadow-parts-1/" target="_blank" rel="noopener noreferrer">CSS Shadow Parts</a> come in!

## Shadow Parts Explained 👻

Shadow Parts give users the ability to target an element inside of a shadow tree from outside of it. In order for the element to be targeted, it has to be exposed by the library author, in this case, Ionic Framework. 

### How ::part works

By setting the part on an element inside of a shadow tree, the element will be exposed to the user for customization. The following change is made inside of the Ionic Framework and requires no action from an end user.

```html
<ion-select>
  #shadow-root
    <div part=”placeholder” class=”select-text select-placeholder”></div>
    <div part=”icon” class=”select-icon”></div>
</ion-select>
```

After adding an `ion-select` in your app, you can customize the placeholder's color using the `placeholder` part:

```css
ion-select::part(placeholder) { 
  color: blue;
  opacity: 1;
}
```

In addition to being able to target the part, you can also target <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-elements" target="_blank" rel="noopener noreferrer">pseudo-elements</a> without them being explicitly exposed:

```css
ion-select::part(placeholder)::first-letter { 
  font-size: 22px;
  font-weight: 500;
}
```

> Vendor prefixed pseudo-elements are not supported at this time. See <a href="https://github.com/w3c/csswg-drafts/issues/4530" target="_blank" rel="noopener noreferrer">this issue on GitHub</a> for more information.

> Most <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes" target="_blank" rel="noopener noreferrer">pseudo-classes</a> are also supported, however, <a href="https://www.w3.org/TR/selectors-4/#structural-pseudos" target="_blank" rel="noopener noreferrer">structural pseudo-classes</a> can not be used with parts. 

As you can see, shadow parts are very powerful because they allow you to edit any property you’d like on an element and/or pseudo-element, without each property being declared as a CSS variable. 

#### Elements inside of a part

It’s important to note that you cannot target elements inside of a part. The following example does *not* work:

```css
/* Does NOT work */
ion-select::part(icon) .select-icon-inner { ... }
```

#### Styling multiple parts

Components can have multiple parts that are added to a single element. For example, take a component with the following parts:

```html
<my-example>
  #shadow-root
    <div part=”tab active”></div>
    <div part=”tab”></div>
</my-example>
```

If you wanted to style the tab that is active, while leaving the inactive tab alone, you would use the following:

```css
my-example::part(tab active) {
  color: green;
}
```
 

## Ionic Framework Parts 💙

Like I said previously, this is very powerful and a HUGE win for customization. Being able to customize any property on an element inside of a shadow root (as long as it has been exposed as a part) takes away the need for CSS variables for every property possible.

With all of that said, let’s go over which parts have been added in Ionic Framework. Our main goal with this initial rollout was to make UI customization easier while discouraging users from modifying elements that are used structurally. As we learned when adding CSS custom properties, we will likely find that more parts are needed to meet more advanced customization needs. This is not the end-all API for Shadow Parts.

### Exposed Parts

- Back Button (`native`, `icon`, `text`) — <a href="https://ionicframework.com/docs/api/back-button#css-shadow-parts" target="_blank" rel="noopener noreferrer">Back Button documentation</a>
- Button (`native`) — <a href="https://ionicframework.com/docs/api/button#css-shadow-parts" target="_blank" rel="noopener noreferrer">Button documentation</a>
- Card (`native`) — <a href="https://ionicframework.com/docs/api/card#css-shadow-parts" target="_blank" rel="noopener noreferrer">Card documentation</a>
- Checkbox (`container`, `mark`) — <a href="https://ionicframework.com/docs/api/checkbox#css-shadow-parts" target="_blank" rel="noopener noreferrer">Checkbox documentation</a>
- Content (`background`, `scroll`) — <a href="https://ionicframework.com/docs/api/content#css-shadow-parts" target="_blank" rel="noopener noreferrer">Content documentation</a>
- Datetime (`text`, `placeholder`) — <a href="https://ionicframework.com/docs/api/datetime#css-shadow-parts" target="_blank" rel="noopener noreferrer">Datetime documentation</a>
- Fab Button (`close-icon`, `native`) — <a href="https://ionicframework.com/docs/api/fab-button#css-shadow-parts" target="_blank" rel="noopener noreferrer">Fab Button documentation</a>
- Img (`image`) — <a href="https://ionicframework.com/docs/api/img#css-shadow-parts" target="_blank" rel="noopener noreferrer">Img documentation</a>
- Item (`native`, `detail-icon`) — <a href="https://ionicframework.com/docs/api/item#css-shadow-parts" target="_blank" rel="noopener noreferrer">Item documentation</a>
- Item Option (`native`) — <a href="https://ionicframework.com/docs/api/item-option#css-shadow-parts" target="_blank" rel="noopener noreferrer">Item Option documentation</a>
- Menu (`container`, `backdrop`) — <a href="https://ionicframework.com/docs/api/menu#css-shadow-parts" target="_blank" rel="noopener noreferrer">Menu documentation</a>
- Menu Button (`native`, `icon`) — <a href="https://ionicframework.com/docs/api/menu-button#css-shadow-parts" target="_blank" rel="noopener noreferrer">Menu Button documentation</a>
- Radio (`container`, `mark`) — <a href="https://ionicframework.com/docs/api/radio#css-shadow-parts" target="_blank" rel="noopener noreferrer">Radio documentation</a>
- Range (`tick`, `tick-active`, `pin`, `knob`, `bar`, `bar-active`) — <a href="https://ionicframework.com/docs/api/range#css-shadow-parts" target="_blank" rel="noopener noreferrer">Range documentation</a>
- Reorder (`icon`) — <a href="https://ionicframework.com/docs/api/reorder#css-shadow-parts" target="_blank" rel="noopener noreferrer">Reorder documentation</a>
- Select (`placeholder`, `text`, `icon`) — <a href="https://ionicframework.com/docs/api/select#css-shadow-parts" target="_blank" rel="noopener noreferrer">Select documentation</a>
- Segment Button (`native`, `indicator`, `indicator-background`) — <a href="https://ionicframework.com/docs/api/segment-button#css-shadow-parts" target="_blank" rel="noopener noreferrer">Segment Button documentation</a>
- Tab Button (`native`) — <a href="https://ionicframework.com/docs/api/tab-button#css-shadow-parts" target="_blank" rel="noopener noreferrer">Tab Button documentation</a>
- Toast (`button`, `container`, `header`, `message`) — <a href="https://ionicframework.com/docs/api/toast#css-shadow-parts" target="_blank" rel="noopener noreferrer">Toast documentation</a>
- Toggle (`track`, `handle`) — <a href="https://ionicframework.com/docs/api/toggle#css-shadow-parts" target="_blank" rel="noopener noreferrer">Toggle documentation</a>

You may notice a lot of components missing from this list. If a component is missing it is likely for one of these reasons:

1. It isn't a Shadow component, thus no parts can be added. If it is a Scoped (such as overlay and input components) or Light DOM component, the child elements can be targeted directly.
2. There are no children elements. For example, `ion-card-header` is a Shadow component, but all styles are applied to the host element. Since it has no child elements, there's no need for parts. 
3. The children elements are mainly structural. In certain components, including `ion-title`, the child element is a structural element used to position the inner elements.
4. We didn't see an immediate need for parts on the component. If you believe there is one missing, please create a <a href="https://github.com/ionic-team/ionic/issues/new/choose" target="_blank" rel="noopener noreferrer">new GitHub issue</a> with as much information as possible so we can make sure the level of customization needed can be achieved.

## Browser Support 💻


CSS Shadow Part support has recently landed in <a href="https://caniuse.com/#feat=mdn-css_selectors_part" target="_blank" rel="noopener noreferrer">all major browsers</a>. However, you can see that some of the browsers still have some usage on unsupported versions. This is something to keep in mind when implementing parts in an app. 

## Try it out 👍

To get started using CSS Shadow Parts, install the latest release of Ionic Framework:

```bash
# for an angular app
npm i @ionic/angular@latest --save

# for a react app
npm i @ionic/react@latest --save
npm i @ionic/react-router@latest --save
npm i ionicons@latest --save

# for a stencil / vanilla JS app
npm i @ionic/core@latest --save
```

> A minimum version of `5.2.0` is required for all of the parts mentioned in this blog.

## Future Work 🔮

Due to the usage of some of the older browser versions without support, we are continuing to support the large amount of CSS custom properties in our components. While we believe that Shadow parts are the way of the future, we will continue to provide and add CSS properties as needed.


If you believe that a CSS variable or part should be exposed that isn’t, please create a <a href="https://github.com/ionic-team/ionic/issues/new/choose" target="_blank" rel="noopener noreferrer">new GitHub issue</a> letting us know.

## Resources 📚

- <a href="https://www.w3.org/TR/css-shadow-parts-1/" target="_blank" rel="noopener noreferrer">CSS Shadow Parts Specification</a>
- <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/::part" target="_blank" rel="noopener noreferrer">::part MDN Documentation</a>
- <a href="https://caniuse.com/#feat=mdn-css_selectors_part" target="_blank" rel="noopener noreferrer">Parts Browser Support</a>
- <a href="https://w3c.github.io/webcomponents/spec/shadow/" target="_blank" rel="noopener noreferrer">Shadow DOM specification</a>
- <a href="https://github.com/fergald/docs/blob/master/explainers/css-shadow-parts-1.md" target="_blank" rel="noopener noreferrer">Explainer: CSS Shadow ::part and ::theme</a>
- <a href="https://meowni.ca/posts/part-theme-explainer/" target="_blank" rel="noopener noreferrer">::part and ::theme, an ::explainer</a>
- <a href="https://ionicframework.com/docs" target="_blank" rel="noopener noreferrer">Ionic Framework Documentation</a>