The design for Netflix’s browse page has remained pretty similar for a few years now. One mainstay component is the preview slider that allows users to scroll through content and hover on items to see a preview.
One unique characteristic of the UI is its hover behavior. When a show preview expands on hover, the cards next to it are pushed outward so that they don’t overlap.
Like this:
We can do this in CSS! No JavaScript. No dependencies. Plain CSS. But before getting into any code, here’s exactly what we want to do:
- The card that is hovered over should expand while keeping its aspect ratio.
- When a card is hovered, the other cards should not change size and move outwards so that they don’t overlap one another.
- All the cards should remain vertically centered with one another.
Sound good? Now let’s get into the code.
HTML and flexible elements
Let’s set up a row of images that represents Netflix’s video previews. That includes:
- A
.container
parent element with several.item
elements inside - Each
.item
element consisting of an image wrapped in an anchor tag - Turning
.container
into a flex container that aligns the items in a row - Setting the flex behavior for the
.item
class so they take up equal space in the row
Expanding an item on hover
Our next step is getting an item to expand when it is hovered. We could do this by animating the element’s width
, but that would affect the flow of the document and cause the hovered item’s siblings to shrink – plus, animating the width
property is known to be poor for performance in some cases.
To avoid squeezing the sibling of the hovered item, we are going to animate the transform
property — specifically, its scale()
function — instead. This won’t affect document flow the same way width
does.
Moving siblings outward
Getting the siblings of a hovered item to move away from the hovered item is the tricky part of this whole thing. One CSS feature we have at our disposal is the general sibling combinator. This lets us select all of the sibling items that are positioned after the hovered item.
We’ll turn to the transform
property’s translateX()
function to move things around. Again, animating transform
is much nicer than other properties that impact document flow, like margins and padding.
Since we’ve set an item to scale up 150% on hover, the translation should be set to 25%. That’s half of the additional space that is being occupied by the hovered item.
.item:hover ~ .item {
transform: translateX(25%);
}
That handles moving things to the right, but how can we translate the items on the left? Since the general sibling combinator only applies to siblings positioned after a given selector (no going “backwards”), we’ll need another approach.
One way is to add an additional hover rule on the parent container itself. Here is the plan:
- When hovering the parent container, shift all the items inside that container to the left.
- Use the general sibling combinator to make the items positioned after the hovered item move to the right.
- Get super specific so a hovered item isn’t translated like the rest of the items.
We’re making a big assumption that your document uses a left-to-right writing mode. If you want to use this effect in a right-to-left context, you will need to set all items inside the hovered outer container to move right and use the general sibling combinator to move all selected items left.
Demo time!
One little thing to note: this final version is using :focus
and :focus-within
pseudo-classes to support keyboard navigation. The Netflix example isn’t using it, but I think that’s a nice touch for accessibility.
There we have it! Yes, we could have used JavaScript event listeners instead of CSS hover rules., and that could possibly be better for maintainability and readability. But it’s sometimes fun to see just how far CSS can take us!
I think it would’ve been good to add some content inside each items (like the netflix example), as that’s a much more useful case, and more tricky to implement.
To implement, you could downscale all of them and then scale the hovered item to 1 so the content inside each card is in its original size.
If the items have content that is visible at all times, this becomes more complicated..
Makes me think of this:
To be honest, that’s probably why Netflix only displays info once the card has fully expanded. Very good point though. I’d like to try this out with some content inside and see if I can get it working.
(sorry my English) i think that was very amazing but if you see you have a problem with the first and last image because when its hover the image dont show it completely, i think you can improve that with son css for the first and last child i let my css.
but again thanks that was very helpful.
https://github.com/DvSemicolon/Nextflix-List
Hey Chris,
This is very cool indeed.
I have made a similar animation on one of my small project (https://github.com/souravbaranwal/superflix).
I would really love to get some feedback.
Live link: https://superflix.sourav.co/ .
Regards,
Sourav
This is great – thanks for sharing!
This is a great example of why you need depth of knowledge of CSS. This comes out being several orders of magnitude faster to implement than Javascript. Nicely reasoned and written; I come here specifically to see how people think their way through design problems and this is a wonderful example.
So helpful. Thank you! Mine cuts off the top on zoom, though. Any chance you could tell my why? https://codepen.io/PixelRelish/pen/eYpzxpw
Hey Sheila – I believe that your issue is related to overflow. If I replace your overflow settings on the .previewScroller container (both x and y) with a basic ‘overflow: visible’ then the images do expand outside the container. You may need to tweak things further to work with the scrollable container though.
Very cool implementation by the way!
Oh, my goodness: Such an easy fix! Thank you, Chris — for the help plus all the great articles. I love CSS Tricks.
Nice. I noticed two things though and I wonder if/how they can be fixed:
When scaling the element (but not translating the siblings), if you quickly hover over an adjacent sibing it will sort of “glitch” with it, fighting to be rendered on top. Can this maybe be fixed with z-index?
When scaling and translating, if you quickly span the cursor over the elements, some white space will briefly form between them. Maybe the transition-duration has to be adjusted in a specific way between translate and scale?
As you may have noticed, Netflix recently (and I use the term loosely) started playing a sample of the video when you hover over it. I’m not asking for an exact replica of how that’s done, I’m just wondering if that’s something achievable with pure CSS/HTML5 or would you likely need to get some JS involved?
Thanks a lot for sharing!! I would add z-index 500ms to the .itm transition property to get rid of that overlapping when an image loses the hover.
Thanks for sharing this cool demo Chris. I was able to use this and add slider animation to it to show more items on click. Thank you for the inspiration :)
Hi! This is really great. Thank you for sharing this. I actually tried this and added in my images from a folder but somehow it’s not working. Can you tell me what are the possible reasons why the hover function won’t work? Thank you.
In Chrome if you move fast between images and then unhover you can see this bug: https://imgur.com/a/QO6NdGq
To fix it add property:
transform: scale(1);
to.item { }
.Should look like this:
.item {
position: relative;
display: block;
flex: 1 1 0px;
transition: transform 500ms;
transform: scale(1);
}
Hello! I am new to CSS… I was aiming to have a netflix carousel. I am using Divi theme and the divi blog carousel module. I add these CSS in the customization, but it is not working. Should I add one (few) classes into the carousel? Thank you very much!
activitesenergotherapie.com
Hi! The trick is great! However Netflix is displaying additional infos on hover. What would be the best way to add other divs when hover?
Thanks