Styling a Shadow DOM in Ionic 4
Since the release of Ionic 4, many of Ionic's components now make use of a Shadow DOM. I've written about what exactly this means in another article, but the basic idea is that a Shadow DOM provides a component with its own isolated world to operate it – free from interference from the parent document. The parent document will not be able to override styles of the component, and styles from the component will not leak out into the parent document.
This isolation between your application and Ionic's web components is, I believe, a fantastic feature. It is not without its downsides, though. Most painfully perhaps that this isolation also means you have less control over exactly how the component will look in your application.
I think this feature has been one of the most highly contentious of the new features Ionic 4 has introduced, and it has caused quite a bit of confusion. In this video, I hope to clear things up a little bit by showing what exactly you can and can't do to a component that uses a shadow DOM, and also the ways in which you can style Ionic's components now.
Watch the video on YouTube: Click here
In this video, we're going to talk about something that I've seen causing quite a bit of confusion in Ionic 4 and that's the use of Shadow DOM in Ionics web components.
So, I've already written an article about this, it goes into quite a bit of depth about what Shadow DOM is and how Ionic is using it. For more detail, I'd recommend reading that Shadow DOM Usage in Ionic Web Component.
In this video, we're just going to have a look at what's happening and try to talk through it a little bit.
So, in previous versions of Ionic, which didn't use Shadow DOM, we had, I guess, more control over exactly how we wanted things to look. So, if we were to add some components like I've added to the page here, I've got a card component and just some range sliders. We'd be able to say select any of these, I'd be able to look it up in the DOM here (0:47), the document object model, and I could just basically change whatever I want with CSS styling. So, if I wanted to come in and change something about this, ion-card-title here, I could open up the inspector here and just change whatever I want.
That's not the case with Ionic 4 and specifically with components that are using Shadow DOM. You can see here (1:08), that there's this thing called shadow root. Basically, what this means is that anything that's inside of this shadow root I have no control over in terms of selecting it with CSS selectors and modifying the styles that way. We're going to have a look at exactly what that means and what that looks like in a second – I'll show you some examples to clarify the concept a little bit.
The general idea behind this is that with Shadow DOM the component gets its own kind of isolated world inside of your document. So, when we insert this web component into a page we know that anything that's inside of here isn't going to be affected by the pages global CSS Styles. If you really have a bunch of CSS Styles for your application – maybe you've got some selectors that are targeting, maybe you're targeting all divs or paragraph tags – if you put some component and you try to drop some component into your application there's a chance that your Styles could affect that component based on the selectors that you already have. On the other side of things, let's say you use one of Ionic's web components and you look at the internals of the component and you think well I don't really like the way this particular thing is sitting, here I want to modify that so you target something inside of that component maybe some CSS selector like item-inner, that kind of thing, and you might add some padding to it or something like that. Now in the future, if Ionic wants to update that component that could break the styling that you've added. Although with Shadow DOM we're kind of losing that ability to just change whatever element we want, we do get benefits in terms of stability and maintainability in that if we drop a web component into our page we know it's going to work and Ionic can safely update those components knowing that they're not going to break a bunch of people's applications.
This doesn't mean that you can't change the styling of the components at all but the only way you can properly change these Styles is by using CSS for variables. So, basically to affect Styles inside of a Shadow DOM we can change the value of variables that are being used inside of that Shadow DOM and by changing that we'll be able to affect the style. We'll only be able to change things that Ionic has specifically used CSS variables for. So, in effect, it's kind of like creating this API that we can interact with the component. Rather than be able to just change absolutely anything we want, we can only interact and change that component in ways that Ionic has set up themselves and that they know that we can change.
So, those are kind of long-winded explanation without really any example so we're gonna look at some examples. Now, hopefully, we can clarify that concept a bit.
<ion-header> <ion-toolbar> <ion-title> Ionic Blank </ion-title> </ion-toolbar> </ion-header> <ion-content padding> <ion-card> <ion-img src="http://placehold.it/500"></ion-img> <ion-card-content> <ion-card-title>Hello World</ion-card-title> <p>The content for this card</p> </ion-card-content> </ion-card> <ion-list> <ion-item> <ion-range color="danger" pin="true"></ion-range> </ion-item> <ion-item> <ion-range min="-200" max="200" color="secondary"> <ion-label slot="start">-200</ion-label> <ion-label slot="end">200</ion-label> </ion-range> </ion-item> <ion-item> <ion-range min="20" max="80" step="2"> <ion-icon size="small" slot="start" name="sunny"></ion-icon> <ion-icon slot="end" name="sunny"></ion-icon> </ion-range> </ion-item> <ion-item> <ion-range min="1000" max="2000" step="100" snaps="true" color="secondary" ></ion-range> </ion-item> <ion-item> <ion-range dualKnobs="true" min="21" max="72" step="3" snaps="true" ></ion-range> </ion-item> </ion-list> </ion-content>
So, if we look at say the ion-content tag that we have here. If I jump into the code you can see we've got a pretty basic kind of layout – we've got ion-content and inside of that, we have a card and a list with a bunch of range sliders. So, the ion-content web component itself actually uses Shadow DOM. So, we can open that up and we can see everything that lives inside of that. If we want a target something with CSS Styles, we can target the host element of the Shadow DOM. So, if we target ion-content here (4:16) we can apply whatever styles we wanted that. If I were to come into the home.page.scss file (4:23) and just say ion-content and we'll just say display:none because that's going to be really obvious. I save that we're going to see the content area disappear. Since this is using Shadow DOM if I want to target anything inside of the Shadow DOM the internals of that web component, I won't be able to do it. You can see here (4:45) right now and content is using something called ion-scroll – this is actually going to be removed in the future I believe but for the sake of this example, let's say I don't want to hide the entire ion-content I just want to hide this to ion-scroll thing for whatever reason.
So, I'll change the ion-content to ion-scroll and we'll try to hide that instead. You can see we've got all of our content back now and so if I just bring up ion-content in the debugger again, we can see that ion-scroll is here and it hasn't been hidden – it's still there and everything's still inside of it. That's because as you can see that's inside of the shadow root which is the Shadow DOM for ion-content. You can kind of even see this little gray box (5:32), I don't know how well it will show up on the video but you can see everything in here has this sort of light gray background and it has this line next to it. Basically, everything in here is hidden from our CSS. If I try to target anything in here it's just not going to work.
This brings up a kind of interesting point and that's since we're using ion-content and we've got all this stuff inside of here (5:56) then well surely all of this is also inside of a shadow root – inside of the Shadow DOM and so we wouldn't be able to target that right? So, let's try targeting ion-card and hiding that because that's inside of ion-content and ion-content is using Shadow DOM and so we shouldn't be able to access this. So, I'll try to hide that ion-card and as you can see it did get hidden. So, our CSS worked in that instance and the reason that that works is because of the use of slots.
If we look at the ion-content web component again here (6:24) and we open the ion-scroll, you can see all these slots that are used everywhere. Basically, a slot is a way to provide some input to a web component. So, when Ionic is creating this component, they set up whatever templates they need, whatever styling they need, and then they say all hey this section here, this is where we want the users content to be injected. You can see that the content ion-card and the ion-lists are inside of the Shadow DOM here but it's actually being projected here through the use of slots. If I click on reveal on the ion-card, you can see that that's actually sitting outside of the Shadow DOM. So, this exists in our DOM and it's being projected inside of the Shadow DOM but that means that we can target this with styles and we can target our ion-list with styles because it's not actually inside the Shadow DOM, it's is being projected there. So, this is saying called slotted content and I have another post on that as well – Understanding How Slots are Used in Ionic 4 – but I just want to highlight why it is that we can target that with CSS but we can't target the internals of the web component itself, we can target our own projected content.
Generally speaking, you know if you have your template and you've added some stuff somewhere in the home.page.html (7:44), you're going to be able to talk about with CSS.
We know now that we can't change the styles of anything inside of a Shadow DOM unless it's being projected there with our own slotted content. As I mentioned, there is a way to change the styles of the internals of the web component. You can change the styles of stuff inside the Shadow DOM by using CSS variables.
Let's just get rid of everything in the home.page.scss file for now and we will discuss exactly how you can do that. In the future, there's going to be documentation around all of the CSS variables that a component users. So, you'll be able to see at a glance what you can change. Now, I don't think that documentation has been released just yet but you can also see what can be changed just by inspecting the component through the debugger here as well.
Let's mess around with this slider for example. So, we have this ion-range-slider (8:39), you can see we've got a color of danger for it there and obviously the easiest way to change the color of this is going to be just to change the primary or something else but for the sake of an example, let's look at how you might change that through CSS variables. Let's inspect this and then we can see we have ion-range which has its own shadow root here, so, this component implements a Shadow DOM which means all of this is hidden from our CSS selectors. Now not every component uses a Shadow DOM by the way but this one does. So, you can see we have a bunch of Shadow DOM's here, we've got the ion-content with its own Shadow DOM, you've got ion-item here (9:17) that has the Shadow DOM, and you've got an ion-range which has its own Shadow DOM as well. This one is a bit more interesting (9:23) because you can sort of see more of the internals of the web component. You can see that Ionic have a bunch of divs set up here and there obviously styling those to get this fancy sliding in kind of the labels and stuff going on there. So, this is an example where people might be like oh hey you know I like this component mostly but I'd really like to change this specific thing here it's not really doing it for me.
So, if I wanted to let's say target range-bar, you can see here (9:55) that you know say the background colors being set there, we've got width, the positioning, all that kind of stuff, and so I might be like, well, I want to change his range-bar active to whatever style I want. Let's say I want to change a range-bar active, I want the background color to be blue. Instead of whatever ion-color base is. You can see here (10:15) that's that red color. What you would usually do saying maybe an Ionic 3 or something like that, you're like ok that's fine, so this uses a class of range-bar active. Let's come into the home.page.scss file (10:25) and we'll target ion-range which is it's a parent there and we'll say range-bar active and we'll just make the background blue and we'll give it an important decoration there so just in case it's being overridden by something else we can see that's not a factor.
Save that, reload the application and we have a lovely red bar still, so it's not blue. We thought that would that would change it to blue and you know probably should in a normal sense without Shadow DOM but in this case, it is still red. The reason for that, as I mentioned before, is that all of this is inside of a shadow root, this is Shadow DOM, and so as far as our CSS and out main document knows, none of this stuff really exists, it doesn't know it's there, it can't target, it can't interact with it. If we try to target range-bar active or anything else in here it's just not going to work. What we can do is modify the CSS variables that are being used in here. We can modify those variables at this level, we can modify the variables in ion-range and that's going to affect everything inside of here (11:36). If we look at that range by active again, we can see that the background is being set by the variable ion-color base. You can sort of see a bunch of variables that are being used if you just filter for – – you can scroll down and you can see all the different kinds of variables that are being used and you can overwrite any of these. So, there's a bunch of stuff that you can modify.
Just for a simple example, let's try and change that background color to blue using this variable here. So, we'll use ion-color base and we need to change a selector, I will get rid of it rather because we can't target range-bar active we can target the parent which is ion-range and then we just set ion-color base to whatever we want that to be. So, we'll say ion-color base and we'll make that blue and we'll see if that works. Actually, we'll have to add the important tag there just to override that specifically. Okay, now you can see that the bar is blue. Let's just inspect that one more time there so if we open up range by active here we can see again it's still using ion-color base as the variable there to set the background but it's been overwritten now. You can see here now in ion-range we're setting ion-color base to blue as opposed to the danger color here because this is using color=danger which is the way it should change this by the way you shouldn't modify the colors using CSS variables like this, this is just an example to show how to use CSS variables. You can see that was setting that ion-base color but now we're setting our own ion-color base and we've set it to blue.
So, we can change any CSS variable that any of these internal parts of the component are using. If we see that in here (13:28), we're like well this is using a – that's interesting one – and ion-item background color, for example, ion-padding, ion-text color, we can just override any of those in our CSS here and it will use that value instead of the default. So, basically, as long as Ionic exposes a way for you to change a particular part of a component, you can do it using CSS variables. If they don't expose a variable to do that then you cannot change it.
Okay, so, hopefully, this video helps clarify the concept of Shadow DOM a little bit and how to use CSS variables and I guess the main point of this video was to talk a little bit about the why things this way now what the purpose of it is and that kind of thing. Again, I haven't already read it I'd recommend reading my Shadow DOM article where it goes into these concepts in a bit more depth and talks about what Shadow DOM is in more depth. It's probably also useful to understand the ease of slots as well Understanding How Slots are Used in Ionic.
Ok, so, if you like this video please do leave a like and consider subscribing. I'll be releasing more Ionic videos in the future and you can also check out my Twitter and my website. Ok, thank you for watching and I'll see you in the next one.