├── .gitattributes
├── README.md
├── index.html
└── style.css
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # netflix-carousel-css-only
2 |
3 | The code from [this YouTube video](https://youtu.be/b--q6Fsf_cA), where I did my best to clone the Netflix carousel UI using CSS only.
4 |
5 | **This uses the `:has()` selector**. If you want to play with the code, make sure that [you're browser supports the `:has()` selector](https://caniuse.com/css-has).
6 |
7 | Don't use anything like this in production until support is much better than when I used it (May 4th, 2022), where it was only supported in Safari and behind a flag in Chrome.
8 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Netflix media carousel CSS only
9 |
10 |
11 |
12 |
13 |
19 |
20 | Trending now
21 |
22 |
181 |
182 |
183 |
184 |
185 |
187 |
188 |
189 |
190 |
192 |
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | }
4 |
5 | *,
6 | *::before,
7 | *::after {
8 | box-sizing: border-box;
9 | }
10 |
11 | body {
12 | font-family: system-ui;
13 | font-size: 1.25rem;
14 | color: white;
15 | background: #121212;
16 | }
17 |
18 | img,
19 | svg {
20 | max-width: 100%;
21 | display: block;
22 | }
23 |
24 | /* general styling */
25 |
26 | .container {
27 | inline-size: min(100% - 4rem, 70rem);
28 | margin-inline: auto;
29 | }
30 |
31 | .flow {
32 | display: grid;
33 | gap: var(--flow-spacer, 1rem);
34 | }
35 |
36 | .page-header {
37 | padding-block: 5rem;
38 | margin-block-end: 5rem;
39 | background-image: url("https://images.unsplash.com/photo-1641353989082-9b15fa661805?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTY0MzM5ODcyOA&ixlib=rb-1.2.1&q=80&w=1200"),
40 | linear-gradient(-25deg, rgb(0 0 0 / 0), rgb(0 0 0 / 1) 50%);
41 | background-size: cover;
42 | background-blend-mode: multiply;
43 | color: white;
44 | }
45 |
46 | .page-title {
47 | font-size: 4rem;
48 | margin: 0;
49 | }
50 |
51 | .section-title {
52 | margin-block: 4rem 1rem;
53 | }
54 |
55 | /* media scroller */
56 |
57 | .media-container {
58 | position: relative;
59 | }
60 |
61 | .media-scroller,
62 | .media-group {
63 | display: grid;
64 | gap: 0.25rem;
65 | grid-auto-flow: column;
66 | }
67 |
68 | .media-scroller {
69 | overflow-x: hidden;
70 | scroll-behavior: smooth;
71 | grid-auto-columns: 100%;
72 | padding: 0 3rem;
73 | scroll-padding-inline: 3rem;
74 | }
75 |
76 | .media-group {
77 | grid-auto-columns: 1fr;
78 | }
79 |
80 | .media-element {
81 | border-radius: 0.25rem;
82 | overflow: hidden;
83 | }
84 |
85 | .media-element > img {
86 | width: 100%;
87 | aspect-ratio: 16 / 9;
88 | object-fit: cover;
89 | }
90 |
91 | .next,
92 | .previous {
93 | display: none;
94 | align-items: center;
95 | z-index: 10;
96 | position: absolute;
97 | width: 3rem;
98 | padding: 1rem;
99 | background: rgb(0 0 0 / 0);
100 | opacity: 0;
101 | }
102 |
103 | .previous {
104 | left: 0;
105 | top: 0;
106 | bottom: 0;
107 | }
108 |
109 | .next {
110 | right: 0;
111 | top: 0;
112 | bottom: 0;
113 | }
114 |
115 | .media-group:first-child :where(.next, .previous) {
116 | display: flex;
117 | }
118 |
119 | .media-scroller:hover :where(.next, .previous) {
120 | opacity: 1;
121 | }
122 |
123 | :where(.next, .previous):hover {
124 | background: rgb(0 0 0 / 0.3);
125 | }
126 |
127 | :where(.next, .previous) > svg {
128 | transition: transform 75ms linear;
129 | transform: scale(1);
130 | }
131 | :where(.next, .previous):hover > svg {
132 | transform: scale(1.2);
133 | }
134 |
135 | .media-group:target :where(.next, .previous) {
136 | display: flex;
137 | }
138 |
139 | .media-scroller:has(:target:not(:first-child))
140 | .media-group:first-of-type
141 | .next {
142 | display: none;
143 | }
144 |
145 | /* navigation indicators */
146 |
147 | .navigation-indicators {
148 | opacity: 0;
149 | position: absolute;
150 | display: flex;
151 | gap: 3px;
152 |
153 | top: -1rem;
154 | right: 2rem;
155 | }
156 |
157 | .navigation-indicators > * {
158 | width: 1rem;
159 | height: 2px;
160 | background: white;
161 | opacity: 0.5;
162 | }
163 |
164 | .media-scroller:has(.media-group:target)
165 | .navigation-indicators
166 | > *:nth-child(1) {
167 | opacity: 0.5;
168 | }
169 |
170 | .navigation-indicators > *:nth-child(1),
171 | .media-group:nth-child(1):target ~ .navigation-indicators > *:nth-child(1) {
172 | opacity: 1;
173 | }
174 |
175 | .media-group:nth-child(2):target ~ .navigation-indicators > *:nth-child(2) {
176 | opacity: 1;
177 | }
178 |
179 | .media-group:nth-child(3):target ~ .navigation-indicators > *:nth-child(3) {
180 | opacity: 1;
181 | }
182 |
183 | .media-group:nth-child(4):target ~ .navigation-indicators > *:nth-child(4) {
184 | opacity: 1;
185 | }
186 |
187 | .media-scroller:hover .navigation-indicators {
188 | opacity: 1;
189 | }
190 |
--------------------------------------------------------------------------------