Welcome to the future of CSS! Just as the ancient Egyptians were pioneers in architecture, we are witnessing a revolution in CSS. Discover modern CSS features from 2020-2024 that are changing the way we style websites!
aspect-ratio allows you to easily create elements with maintained proportions without padding hacks!1/* Old hack with padding-bottom */
2.video-container {
3 position: relative;
4 padding-bottom: 56.25%; /* 16:9 = 9/16 = 0.5625 = 56.25% */
5 height: 0;
6}
7
8.video-container iframe {
9 position: absolute;
10 top: 0;
11 left: 0;
12 width: 100%;
13 height: 100%;
14}1/* Modern way - simple and readable */
2.video-container {
3 aspect-ratio: 16 / 9;
4 width: 100%;
5}
6
7.video-container iframe {
8 width: 100%;
9 height: 100%;
10}Egyptian analogy: Like the perfect proportions of the Great Pyramid (4:3) - maintained regardless of scale!
1.video-wrapper {
2 aspect-ratio: 16 / 9;
3 width: 100%;
4 max-width: 800px;
5 margin: 0 auto;
6}
7
8.video-wrapper iframe {
9 width: 100%;
10 height: 100%;
11 border: none;
12}1.card-image {
2 aspect-ratio: 4 / 3;
3 width: 100%;
4 object-fit: cover;
5 border-radius: 8px;
6}1.avatar {
2 aspect-ratio: 1; /* 1:1 = square */
3 width: 100px;
4 border-radius: 50%;
5 object-fit: cover;
6}1.golden-box {
2 aspect-ratio: 1.618; /* Golden ratio */
3 width: 100%;
4 background: goldenrod;
5}1.instagram-post {
2 aspect-ratio: 1 / 1; /* Instagram square */
3 width: 100%;
4 max-width: 600px;
5}1.phone-mockup {
2 aspect-ratio: 9 / 19.5; /* iPhone proportions */
3 width: 375px;
4 border: 10px solid black;
5 border-radius: 30px;
6}gap now works in both Grid and Flexbox! (since 2021)1/* Grid - always supported */
2.grid {
3 display: grid;
4 grid-template-columns: repeat(3, 1fr);
5 gap: 20px; /* Row and column gap */
6}
7
8/* Flexbox - since 2021! */
9.flex-container {
10 display: flex;
11 gap: 20px; /* Space between items */
12}1/* Navigation with gap */
2.nav {
3 display: flex;
4 gap: 1.5rem;
5 align-items: center;
6}
7
8/* Button group */
9.button-group {
10 display: flex;
11 gap: 0.5rem;
12}
13
14/* Form fields */
15.form {
16 display: flex;
17 flex-direction: column;
18 gap: 1rem;
19}
20
21/* Card grid */
22.cards {
23 display: grid;
24 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
25 gap: 2rem 1rem; /* 2rem row, 1rem column */
26}New pseudo-classes allow for elegant grouping!
1/* Instead of: */
2h1 a,
3h2 a,
4h3 a,
5h4 a,
6h5 a,
7h6 a {
8 color: goldenrod;
9}
10
11/* Use :is() */
12:is(h1, h2, h3, h4, h5, h6) a {
13 color: goldenrod;
14}Specificity of
: Takes the highest specificity from the list!:is()
1/* Specificity = ID (100) */
2:is(#id, .class, div) {
3 color: red;
4}1/* Specificity = 0! */
2:where(h1, h2, h3) {
3 margin-top: 0;
4}
5
6/* Easy to override */
7h2 {
8 margin-top: 1rem; /* This wins because :where() has 0 specificity */
9}1/* Reset for headings */
2:where(h1, h2, h3, h4, h5, h6) {
3 margin: 0;
4 font-weight: 600;
5}
6
7/* Link states */
8:is(a:hover, a:focus) {
9 text-decoration: underline;
10 color: goldenrod;
11}
12
13/* Form inputs */
14:is(input, textarea, select):focus {
15 outline: 2px solid #3498db;
16 outline-offset: 2px;
17}
18
19/* Lists */
20:where(ul, ol):where(:not([class])) {
21 padding-left: 1.5em;
22}:has() is a revolution - it lets you style the parent based on children!
1/* Style article that HAS an image */
2article:has(img) {
3 display: grid;
4 grid-template-columns: 1fr 2fr;
5}
6
7/* Style card that HAS a button */
8.card:has(.button) {
9 padding-bottom: 4rem;
10}
11
12/* Style form that HAS an error */
13form:has(.error) {
14 border: 2px solid red;
15}1/* Card with image - different layout */
2.card:has(img) {
3 display: flex;
4 flex-direction: row;
5}
6
7/* Card without image - normal layout */
8.card:not(:has(img)) {
9 text-align: center;
10}1/* Form field with error */
2.form-field:has(input:invalid) {
3 border-color: red;
4}
5
6.form-field:has(input:valid) {
7 border-color: green;
8}
9
10/* Label when input has focus */
11.form-field:has(input:focus) label {
12 color: #3498db;
13 font-weight: bold;
14}1/* Style label when checkbox is checked */
2.checkbox-wrapper:has(input:checked) label {
3 font-weight: bold;
4 color: green;
5}
6
7.checkbox-wrapper:has(input:checked) {
8 background: #e8f5e9;
9}1/* Highlight section in navigation */
2nav:has(~ main #section-1) .nav-link[href="#section-1"] {
3 color: goldenrod;
4 font-weight: bold;
5}1/* Style container when empty */
2.list:not(:has(li)) {
3 display: flex;
4 align-items: center;
5 justify-content: center;
6}
7
8.list:not(:has(li))::before {
9 content: "No items found";
10 color: #999;
11}subgrid allows children to use the parent's grid!1.parent-grid {
2 display: grid;
3 grid-template-columns: repeat(4, 1fr);
4 gap: 20px;
5}
6
7.child-grid {
8 display: grid;
9 /* Use columns from parent! */
10 grid-template-columns: subgrid;
11 grid-column: span 2;
12}1<div class="product-grid">
2 <article class="product">
3 <h3>Product 1</h3>
4 <div class="product-details">
5 <span class="price">$99</span>
6 <button>Buy</button>
7 </div>
8 </article>
9</div>1.product-grid {
2 display: grid;
3 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
4 gap: 2rem;
5}
6
7.product {
8 display: grid;
9 /* Align all .product-details on the same line */
10 grid-template-rows: auto 1fr auto;
11}
12
13.product-details {
14 display: grid;
15 grid-template-columns: subgrid;
16 grid-column: 1 / -1;
17 align-items: center;
18}@layer allows you to control the cascade without specificity!1/* Define layer order */
2@layer reset, base, components, utilities;
3
4/* Layer reset - lowest priority */
5@layer reset {
6 * {
7 margin: 0;
8 padding: 0;
9 }
10}
11
12/* Layer base */
13@layer base {
14 body {
15 font-family: Arial, sans-serif;
16 }
17}
18
19/* Layer components */
20@layer components {
21 .button {
22 background: #3498db;
23 color: white;
24 }
25}
26
27/* Layer utilities - highest priority */
28@layer utilities {
29 .text-center {
30 text-align: center;
31 }
32}Container Queries allow you to style based on the size of the container, not the viewport!
1.card-container {
2 container-type: inline-size;
3 container-name: card;
4}
5
6.card {
7 padding: 1rem;
8}
9
10/* When container > 400px */
11@container card (min-width: 400px) {
12 .card {
13 display: flex;
14 gap: 1rem;
15 }
16
17 .card img {
18 width: 150px;
19 }
20}
21
22/* When container > 600px */
23@container card (min-width: 600px) {
24 .card {
25 padding: 2rem;
26 }
27}1.element {
2 /* Mix 50% blue with 50% red */
3 background: color-mix(in srgb, blue 50%, red);
4
5 /* Lighten a color */
6 background: color-mix(in srgb, #3498db, white 20%);
7
8 /* Darken a color */
9 background: color-mix(in srgb, #3498db, black 20%);
10}1/* Change color of checkboxes, radio, range */
2input[type="checkbox"],
3input[type="radio"],
4input[type="range"] {
5 accent-color: goldenrod;
6}
7
8/* Globally */
9:root {
10 accent-color: #3498db;
11}1.element {
2 /* Like overflow: hidden, but doesn't create a scroll context */
3 overflow: clip;
4}1/* Instead of: */
2.element {
3 top: 0;
4 right: 0;
5 bottom: 0;
6 left: 0;
7}
8
9/* Use inset */
10.element {
11 inset: 0;
12}
13
14/* Or per-side */
15.element {
16 inset: 10px 20px; /* top/bottom left/right */
17}1img {
2 object-view-box: inset(10% 20% 10% 20%);
3 /* Crops the image from all sides */
4}1.long-section {
2 /* Don't render outside viewport - HUGE performance boost! */
3 content-visibility: auto;
4 contain-intrinsic-size: 0 500px;
5}| Feature | Chrome | Firefox | Safari | Edge | |---------|--------|---------|--------|------| | aspect-ratio | 88+ (2020) | 89+ (2021) | 15+ (2021) | 88+ | | gap in Flexbox | 84+ (2020) | 63+ (2018) | 14.1+ (2021) | 84+ | | :is() | 88+ | 78+ | 14+ | 88+ | | :where() | 88+ | 78+ | 14+ | 88+ | | :has() | 105+ (2022) | 121+ (2023) | 15.4+ (2022) | 105+ | | subgrid | 117+ (2023) | 71+ (2019) | 16+ (2022) | 117+ | | @container | 106+ (2022) | 110+ (2023) | 16+ (2022) | 106+ | | @layer | 99+ (2022) | 97+ (2022) | 15.4+ (2022) | 99+ |
Most important modern features:
Why use them:
Remember: Modern CSS is like discovering new hieroglyphs - new possibilities, simpler solutions, more powerful tools!