The following is a guest post by Sara Soueidan. I know Sara through all her excellent work on CodePen. She was working on some custom HTML5 video controls and noticed that the customizations were lost when the video went into full screen mode (example of that happening). Digging into the shadow DOM, Sara finds a solution…
If you’ve ever worked with HTML5 video then you have probably wondered how you get a bunch of control buttons, sliders, and slider thumbs on your page, when you’ve only added a single <video>
tag to the DOM.

Browsers add these controls as a “sub-tree” of the video tag into the rendering of the document. These elements (buttons, sliders, etc.) are part of the DOM, but you can’t actually see them in the main DOM tree, you only see them rendered onto the page. More on this shortly.
The problem of HTML5 video controls in full-screen mode
While working on a custom HTML5 video framework lately, I stumbled upon an issue which a lot of designers and developers stumble upon in this area. Instead of displaying the custom controls I was working on, native browser controls appeared on the video when it entered the full-screen mode. Like so many others, I searched around for answers to this problem but had no luck finding any. (Update: this bug is filed in the Chrome bug tracker).
After some inspection in the dev tools, I found that:
- The native controls were still there. By setting the
controls
attribute of the video element tofalse
, we are able to hide the controls but for some reason, when entering full-screen mode they reappear, despite being hidden in normal screen mode. (Why?) - The custom controls were hidden below the video in the full-screen mode. Inspecting the controls with the dev tools showed that the reason the controls were hidden below is because the user agent’s style sheet (in this case Chrome’s style sheet) was overriding the styles applied to the controls, with a very weird
z-index
value, I must say!

How do we make this work? How do we prevent the native controls from appearing in full-screen mode and show our own custom-styled controls instead?
The second point is easy: just override the user agent stylesheet. We do that all the time in our stylesheets. We’ll get to this shortly. But what about the first point? How can we hide elements that the browser adds but we can’t see in the DOM tree we’re working with?
Note that the technique explained in this article to hide the native controls works only in browsers that support the Shadow DOM.
Quick Introduction to Shadow DOM
The subtree of DOM elements generated by the browser is what we call the “Shadow DOM”. Plainly speaking, it’s a bunch of DOM elements, the same as the ones you’re already familiar with, like <div>
s and <span>
s, which are added by the browser as a document fragment, and are rendered on the page just like the main DOM tree.
James Edwards summarizes the function of the shadow DOM perfectly in his article for SitePoint:
The Shadow DOM encapsulates content by creating document fragments. Effectively, the content of a Shadow DOM is a different document, which is merged with the main document to create the overall rendered output.
In fact some browsers already use this to render some of their native widgets.
The reason why browsers do this is because browser developers decided to encapsulate some DOM elements to hide them from us developers so we’re not concerned with the implementation details of these elements, in an attempt to make things easier for us to work with. Also, as James Edwards also said in his article:
Because it’s isolated, users can’t accidentally break it, there’s no possibility of naming conflicts with any classes or IDs you use, and the CSS on the main page won’t affect it at all.
So we know now that the controls added to the video tag are just part of the shadow DOM sub-tree generated for this tag by the browser.
Hiding the native video controls
We need to be able to style the controls which are part of the shadow DOM, but how do we do that if the regular CSS selectors we know can’t access Shadow DOM elements?
After reading this superb intro article by Dimitri Glazkov I learned that “there’s a handy pseudo attribute capability, which allows shadow DOM subtrees to associate an arbitrary pseudo-element identifier with an element in the subtree.”
Which means that some elements inside the shadow DOM subtree can be styled by targeting them via their associated pseudo-element. This sounds great!
But how do we determine what pseudo-element is associated with the shadow DOM element we need to style? Some elements are more or less known, like the range input, which has a pseudo-element available to style its thumb in webkit browsers
::-webkit-slider-thumb
Firefox also provides two pseudo-elements to style range inputs now that it supports them starting from version 23.0:
::-moz-range-track
and
::-moz-range-thumb
But what about other less-known pseudo-elements associated with other shadow DOM elements? What pseudo-elements are associated with them? To find out, the Chrome dev tools come to the rescue!
Determining pseudo-elements associated with Shadow DOM elements
One of the great features of the Chrome dev tools is that you can inspect the shadow DOM subtrees in the Elements panel just like you would inspect the “regular” DOM tree. All you have to do is enable this feature:
- Go to dev tools settings (by clicking the little gear icon at the bottom right of the dev tools)
- In the General tab, check the “Show Shadow DOM” option
- Restart the dev tools (close and reopen)
Now, when u inspect the <video>
element, you get something similar to the screenshot below:

<video>
element as inspected with the Chrome dev tools reveals a new #document-fragmentThen, just like you can get the style rules for any HTML element if you click on it in the Elements panel, you can see the pseudo-elements associated with the subtree of the shadow DOM too. Pretty neat, huh? :)

<div>
inside the shadow DOM sub-treeSo for the video controls we can see from the screenshot that there’s a pseudo-element called ::-webkit-media-controls
, which as the name clearly indicates, is associated with tags containing media controls.
Setting a display:none !important;
on this pseudo-element hides the element completely in normal and full-screen mode.
::-webkit-media-controls {
display:none !important;
}
But bear in mind that this pseudo-element is associated with the outermost <div>
in the subtree, which will contain media controls, all kinds of media controls, which means that if you have an <audio>
tag somewhere on the page, you’ll be hiding the controls for that media element as well.
So, unless you want to hide all browser native media controls, you’ll need to specify which type of media controls to hide, which are those associated with the video element, and you’d target them by specifying the “scope” for this pseudo-element:
video::-webkit-media-controls {
display:none !important;
}
This will hide the native controls completely.
Another options is that you could go deeper in the sub-tree to find the pseudo-element associated with the inner and more specific div
containing the actual controls: buttons, sliders, etc., and hide that.
So going one level deeper we get the pseudo-element associated with the inner div
:
video::-webkit-media-controls-enclosure {
display:none !important;
}
Setting the display
property of this pseudo-element to none
will hide the native controls completely, in both normal and full-screen mode. You can also see that this pseudo-element is more specific in terms of scope (the video element) and the div
it’s associated with: the div
“enclosing” the actual control elements.
And that’s pretty much all you need to do to hide the native controls in full-screen mode. Simple, right?
Showing the custom video controls
As for the custom controls, they still didn’t appear in my demo after hiding the native ones, because, as you’ve seen in the screenshot above, the user agent style sheet had a value for z-index set to the full-screen mode:

I have no idea why the developers chose this value in particular, but it could be because, as Nate Volker pointed out in his comment below that this number is the maximum value for a 32-bit signed integer, which could be the datatype the browser developers use to represent values for z-index. So in order to make sure the custom controls are visible you need to set the z-index for the custom controls to be equal to or higher than this value.
Setting it to 2147483646 made the controls disappear in full-screen mode on Firefox and, sometimes (a bug?), in Chrome, so the z-index for the controls container should be >= 2147483647.
.custom-video-controls {
z-index: 2147483647;
}
Summary
In order to hide the native controls in full-screen mode in browsers supporting the Shadow DOM you need to:
- target the pseudo-element associated with them:
video::-webkit-media-controls-enclosure
, which you can find by using Chrome’s dev tools and inspecting the shadow DOM, and set it’s display to none, and then to show your custom-styled controls - set the z-index of your custom controls to a value higher than the z-index supplied by the user agent style sheet.
And that’s pretty much it!
Demo
See the Pen Custom HTML5 Video Controls in Full Screen by Chris Coyier (@chriscoyier) on CodePen.
What about other browsers that don’t support the shadow DOM?
While working I also tested the results in Firefox. Setting the z-index
to the value mentioned above makes the custom controls also appear in Firefox’s full-screen mode, so showing the controls is easy, but the shadow DOM solution doesn’t work because Firefox does not yet support it, so the native controls also still appear in full-screen mode.
Surely enough, you can expect similar behavior in other browsers that don’t support the shadow DOM.
The only way that I found to hide the native controls in these browsers is to cover the native controls with the custom ones, by simply positioning the custom controls on top of them using simple and basic CSS styles targeting the full-screen mode.
This hides the native controls, but also has a limitation: the custom controls can’t have a transparent background, otherwise the native controls will show through.
There is another possible solution here. Mr. James Edwards noted in his comment below that the native controls can be hidden in full-screen mode in another way too, without having to use the shadow DOM, by applying the full-screen mode to an element, for example, a
Further Reading
- W3C’s Shadow DOM Working Draft
- Shadow DOM 101 on HTML5Rocks
- The Dark Shadow Of The DOM on SitePoint
- What The Heck Is Shadow DOM by Dimitri Glazkov
And if you’re interested you can also see how you can use the shadow DOM API and HTML <template>
s to create your own “encapsulated” code and HTML templates by watching this presentation by Peter Gasston about Web Components that he gave at this year’s CSSConfEU.
I love it Sara! You are a genius.
i love that video in demo :)
Indeed – thank you veeeery much!
2147483647 is the maximum value for a 32-bit signed integer, which is likely the datatype the browser developers use to represent values for z-index.
That’s a good tip! Thanks, Nate! I was very curious about it. :)
I’ve built an HTML5 video player which supports fullscreen mode, and I never encountered any of these issues.
When you apply fullscreen mode, are you applying it to the VIDEO element? If so, that’s the problem — if you wrap the VIDEO in a container DIV and then apply fullscreen to that container, then it all works as expected, in all browsers that support fullscreen mode (i.e. recent versions of Chrome, Safari, Firefox and Opera).
Here: http://www.accessibilityoz.com.au/products/ozplayer/
Thanks for the comment, James!
In the demo I provided for this article I had to apply the full-screen mode to the video’s container to position the custom controls where I wanted them to be (in Firefox), but that still didn’t solve the problem of the native controls. The native controls were still there in full-screen on Firefox.
Applying full-screen to the video container does work in Chrome, I just tested it, but it doesn’t work in Firefox for me. :/
Edit to my previous comment:
My bad! it does work in Firefox, too! Thanks for the tip, James. :)
I can confirm your findings, James. In my case though, I have the video in one container and everything else in a following container. Both containers are then wrapped again. This wrapper is “fullscreened”. The parallel containers fixed the z-index issue without me ever noticing the problem – plus it does not require Shadow DOM support or me knowing about all the browser specific pseudo selectors.
Cheers
2147483647 = 2^31 – 1
Thanks for the post…I am a hack at best…and, doing all my stuff in WordPress now….my Holy Grail is how to push the controls down below the video frame. Is that a total FoPAH or can it be done?
That should be doable by giving the controls a negative margin at the bottom. I haven’t tried it but it could do it.
The great sara strikes again :D
Awesome as always.
http://www.nilnakliyat.com/ Thanks for the comment, James!
FWIW IE11 does support the Full-screen API, prefixed (according to Can I use…).
Cool, so this means that the full-screen applied to the container could/should work in IE11, but the shadow DOM technique may or may not work, as support for the shadow DOM is still unknown in IE11 (also according to http://caniuse.com/shadowdom)
Nice post! Thank you!!!
I don’t believe this is a problem, if you go ahead and make the container element fullscreen.
The one containing both the controls and the video. This might actually make it easier to style the video and the controls in fullscreen.
This is what I’m currently doing, as I’m in the process of making a custom video player as well.
This is definitely an interesting approach, but it’s not a very robust one. The demo works properly in Chrome, but in the latest Safari beta, the custom controls are there along with the native ones.
As some of the other commenters said, the better solution is to put the wrapper element in full screen. It also has the benefit of being supported in more browsers. I wrote a library that we use in the Vimeo player to make it a little easier to deal with all the variations and bugs (there are a few that specifically affect iframes).
Wahooo…! Good Job done by Sara i really appreciated
finally I can come over my video controls width problem, thanks for the contributor! :)
Great post.. Thanks to Sara and you !!
Amazing article :)
I have applied this solution in to my player. but unfortunately Firefix doesn’t show the custom control in full width :(
Demo http://www.athimannil.com/player/