CSS IS AWESOME
ScrollA dive into CSS awesome features
With this website, I wanted to highlight my favorite things we can do with CSS today. The list is not finished - there is a lot of amazing things we can do with CSS nowadays and in the future - but I think it's a good start. Happy reading !
Skip to :
Relative units
Relative units are one of the main powerful features of CSS : thanks to them, we can easily display responsive and accessible layouts. Let's review the most used and interesting relative units.
EM
An em
is equal to the font size of an element or
the font size of that element’s nearest ancestor.
For example, we know that 1em
refers to the font size.
So if the parent's font-size value of an element is 16px
,
1em
equals 16px
for that element. So far, so good.
But if the element itself declare a
font-size, like 12px
, then 1em
equals 12px
.
/* big purple box */ .parent { font-size: 20px; } /* medium pink box */ .child-one { /* the child hasn't declared a font size, so 1em = parent's font size */ width: 2em; /* ↑ 2 x 20px = 40px */ height: 2em; /* ↑ 2 x 20px = 40px */ } /* small green box */ .child-two { font-size: 0.5em; /* ↑ 0.5 x 20px = 10px */ /* the child has declared a font size, so 1em = element's font size */ width: 2em; /* ↑ 2 x 10px = 20px */ height: 2em; /* ↑ 2 x 10px = 20px */ }
When should we use em ?
As you can see with the example above, the em
unit can be tricky.
It's best to use em
for typography and spacing on textual components, like
a button or input fields.
REM
The unit rem
stands for root em.
By "root", it means that 1rem
equals the font size of the HTML element
(which for most browsers has a default value of 16px
).
/* big purple box */ .parent { width: 12.5rem; height: 12.5rem; } /* medium pink box */ .child-one { width: 2.5rem; height: 2.5rem; margin-right: 0.5rem; } /* small green box */ .child-two { width: 1.125rem; height: 1.125rem; }
When should we use rem ?
Unlike the em
unit, the rem
unit is global and constant.
It means that its value will not change, no matter how deeply the element is nested.
For that reason, it's best to use rem
for properties that will allow elements to keep
the
same
proportion
on every viewports : font size, spacing, box size, etc.
VH, VW, VMIN & VMAX
The units vh
, vw
vmin
and vmax
are viewport units. It means that they are relative to the browser window or the device
screen
size.
In short, 1vh
= 1% of the viewport height,
whereas 1vw
= 1% of the viewport width.
With the definition of these two units,
we can easily guess the role of the other two : the viewport minimum vmin
will return
the
smaller value of
vh
or vw
, and the viewport maximum vmax
will return the
larger
value
of
vh
or vw
.
In summary, 1vmin
= 1% of the smaller value between
1vw
or 1vh
,
whereas 1vmax
= 1% of the larger value between
1vw
or 1vh
.
/* 💡 the boxes will update their sizes as you resize your browser window */ /* big purple box */ .parent { width: 19vw; height: 19vw; /* ↑ we use vw (and not vh), because we want a square */ /* and the height and the width of a viewport are always different */ } /* medium pink box */ .child-one { width: 4vw; height: 4vw; } /* small green box */ .child-two { width: 2vmax; /* ↑ In short, it will chose 2vw on desktop and 2vh on mobile */ height: 2vmax; }
When should we use viewport units ?
Viewport units are useful for creating elements that will occupy a constant
portion of the screen. For example, it can be useful for displaying a full-height
homepage (with 100vh
), or to ensure that a certain element stays visible on the screen.
Viewport units can also be combined with the calc()
function, to allow, for example,
font size to scale in response to a change in the window size.
Overall, viewport units are ideal for creating proportionate and responsive elements.
CH
The ch
unit is less used and known than the aforementioned units,
but could be just as much useful. It represents the width of the "0" character of the
selected font.
So 1ch
equals roughly one character. How can we use this information, then ? Well,
we
can use the ch
unit to set a max-width or
a min-width, for readability and consistency.
/* purple box */ .element { max-width: 50ch; } /* green buttons */ .button { min-width: 20ch; }
When should we use the ch unit ?
The ch
unit comes in handy for setting a max-width or a min-width.
Used with paragraphs and articles, it improves readability : the optimal line length for a body text
is
believed to be between 50 and 75 characters.
Like the above examples, the ch
unit can also be used to set a min-width to buttons or
navigation items.
Relative units and media queries
With what we learn so far, we can use the relative units to minimize the use of media queries :
:root { /* ensures the font-size never drops below the default (system/browser/user defined) value */ font-size: calc(1rem + 0.5vw); } /* media queries in pixels */ @media (min-width 960px) { :root { /* ↓ Upscale by 25% at 960px */ font-size: 125%; } } h2 { /* ↓ 2.5 × the root font-size */ font-size: 2.5rem; } h2 strong { /* ↓ now 1.125 × 2.5rem */ font-size: 1.125em; } .big-responsive-button { /* ↓ clamp(min-value, preferred value, max-value) */ font-size: clamp(1.5rem, 3vw, 2.5rem); /* ↓ responsive properties (will adapt to button's font size) */ padding: 1.5em; border-radius: 1.5em; } article, p { /* ↓ better readability */ max-width: 60ch; }
Relative units : further resources
Variables
Variables are powerful sidekicks. It gives us the power of sharing and customize reusable code and the ability the ability to adapt the user interface to user interactions.
How does variables works ?
CSS variables are custom properties.
We declare a variable with the --
prefix, for example --my-variable
,
and we get its value by calling the var()
function, like this :
var(--my-variable,
fallback value)
. We use variables for creating color palettes, for calculations,
for displaying multiple themes and so much more.
Examples
Color palettes with HSL
:root { /* Here are the global variables, shared with all elements of the document /* --hue: 336; /* we declare a main color, a number between 0 and 360 /* ... } :root[theme="dark"] { /* we then use the variable in the HSL representation */ --main-color: hsl(var(--hue), 100%, 50%); /* HSL stands for --hue, --saturation and --lightness */ --constrast-color: hsl(var(--hue), 60%, 40%); /* we can change the saturation or the lightness to create different shades */ --dark-color: hsl(var(--hue), 40%, 10%); ... } .section button { background-color: var(--constrast-color, black); /* ↑ 💡 we use a fallback value, in this example "black" */ ... }
Light theme / Dark theme
:root { --hue: 336; /* main color, 336 by default */ /* 💡 the value changes when the user chooses another color */ ... } :root[theme="light"] { --background-color: hsl(var(--hue), 35%, 82%); /* ↑ 💡 backgroud is light */ --text-color: hsl(var(--hue), 90%, 10%); /* ↑ 💡 text is dark */ ... } :root[theme="dark"] { --background-color: hsl(var(--hue), 20%, 20%); /* ↑ 💡 background is dark */ --text-color: hsl(var(--hue), 20%, 80%); /* ↑ 💡 text is light */ ... } p { color: var(--text-color); /* ↑ 💡 will change on user interaction */ ... } section { background-color: var(--background-color, lightgrey); /* ↑ 💡 we use a fallback value, in this example "lightgrey" */ }
Inheritance
.parent { --background-color: red; /* local variable (not in :root), inherited by children */ } .child-one { background-color: var(--background-color, grey); /* by default, background-color: red */ } .child-two { --background-color: green; /* local variable, override the parent variable */ background-color: var(--background-color, grey); /* background-color: green */ }
Inline style and variables
/* I want to add more margin-top to this subtitle */ /* I pass the new margin-top with the inline style */ <h2 style="--margin-top: 5rem;" class="subtitle"> Awesome Subtitle </h2> .subtitle { --margin-top: 2.5rem; /* the margin-top by default */ /* is overrided by the new value declared in the inline style */ margin: var(--margin-top, 2.5rem) 0 1rem 0; /* so, for the title named "Awesome Subtitle", margin: 5rem 0 1rem 0; */ }
Variables : further resources
Layout
Modern CSS gives us tools to easily display responsive and customized layouts, without the help of CSS libraries. Flexbox and Grid are the most powerful ones.
GRID
CSS Grid Layout is a tool released in 2017. It's a two-dimensional grid-based layout system and was created to solve a common problem : how to control every region of a page, without the need for "hacking" solutions.
I won't dive into every feature of the CSS Grid Layout, but I will show you some examples, along with comments.
If you want to learn the basics of CSS Grid Layout, I highly recommend this tutorial : Learn CSS Grid - A Guide to Learning CSS Grid
CSS Grid Basics
align-self: start;
align-self: center;
align-self: end;
/* white container */ #grid-container { display: grid; grid-template-columns: repeat(3, 1fr); /* 💡 ↑ defines width of columns */ /* 1fr(action) 1fr 1fr => 3 columns of 33% */ grid-template-rows: 8rem auto minmax(auto, 50%); /* 💡 ↑ defines height of rows */ /* 3 different-sized rows */ gap: 0.5rem; /* 💡 ↑ gutters */ } .grid-items-one:nth-child(1) { justify-self: start; align-self: start; } .grid-items-one:nth-child(2) { justify-self: center; align-self: center; } .grid-items-one:nth-child(3) { justify-self: end; align-self: end; } .grid-items-one:nth-child(4) { justify-self: stretch; grid-column-start 2; }
CSS Grid Responsive
/* white container */ #grid-container { --minColWidth: 30ch; /* ↑ minimum size of an element of a grid */ resize: horizontal; overflow: scroll; width: 100%; } /* the four grids */ .grids { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--minColWidth), 1fr)); gap: 0.5rem; margin-bottom: 0.5rem; }
CSS Grid Positioning and Spanning
grid-column: 2 / span 2;
grid-row: 2 / 4;
grid-row: 2;
grid-column: span 3;
/* white container */ #grid-container { display: grid; grid-template-columns: repeat(3, 1fr); width: 100%; } .grid-items:nth-child(2) { order: 1; grid-column: 2 / span 2; } .grid-items:nth-child(3) { grid-column: 1; grid-row: 2 / 4; } .grid-items:nth-child(4) { grid-column: 3; grid-row: 2; } .grid-items:nth-child(5) { grid-area: 1 / 1 / 2 / 3; } .grid-items:nth-child(7) { order: 2; grid-column: span 3; }
CSS Grid Area (with Media Queries)
grid-column: footer-start / span 2;
grid-row-start: footer-start;
/* for simplicity, we start with the minimum size first */ /* that way, we don't have to, for bigger sizes and some properties, /* reset the value to "initial" */ /* we set the basics of the grid and then we add extra features on other viewports */ #grid-container { display: grid; grid-template-areas: "header" "main" "sidebar" "." "." "footer"; gap: 0.5rem; width: 100%; } .grid-items:first-child { grid-area: header; } .grid-items:nth-child(2) { grid-area: main; } .grid-items:nth-child(3) { grid-area: sidebar; } .grid-items:nth-child(4) { grid-row-start: sidebar-end; grid-column: 1 / 1; } .grid-items:nth-child(5) { z-index: 1; grid-row-end: footer-start; grid-column: 1 / 1; } .grid-items:last-child { grid-area: footer; } /* tablet layout */ @media (min-width: 768px) { #grid-container { grid-template-columns: auto 30%; /* 30% of the container for the sidebar */ grid-template-rows: 1fr; grid-template-areas: "header header" "main sidebar" "main sidebar" ". ." "footer footer" "footer footer" "footer footer"; } .grid-items:nth-child(4) { grid-row-start: main-end; grid-column: 1 / span 2; } .grid-items:nth-child(5) { z-index: 1; grid-column: footer-start / span 1; grid-row: 7 / span 1; } } /* desktop layout */ @media (min-width: 1200px) { #grid-container { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(4, 1fr); grid-template-areas: "header header header header" "main main . sidebar" "main main . sidebar" "footer footer footer footer"; } .grid-items:nth-child(4) { grid-area: 3 / 2; } .grid-items:nth-child(5) { z-index: 1; grid-column: footer-start / span 2; grid-row-start: footer-start; } }
CSS Grid : further resources
- Learn CSS Grid - A Guide to Learning CSS Grid | Jonathan Suh
- A Complete Guide to Grid | CSS-Tricks
- Solutions to Replace the 12-Column Grid | Modern CSS Solutions
- Things I've Learned About CSS Grid Layout | CSS-Tricks
- 3 CSS Grid Techniques to Make You a Grid Convert | Modern CSS Solutions
- Container Query Solutions with CSS Grid and Flexbox | Modern CSS Solutions
- CSS Frameworks Or CSS Grid: What Should I Use For My Project? — Smashing Magazine
- Best Practices With CSS Grid Layout — Smashing Magazine
- Concise Media Queries with CSS Grid
- Grid by Example - Usage examples of CSS Grid Layout
- Look Ma, No Media Queries! Responsive Layouts Using CSS Grid
Shape Outside
The shape-outside property defines a shape (circle, ellipse, polygon, rectangle) for an element, and therefore how content will wrap around that element.
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Consequatur autem quae sapiente? Nulla libero reprehenderit praesentium aut amet expedita voluptas exercitationem dolore minus ut. Itaque tempora quam vel unde laboriosam.
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Consequatur autem quae sapiente? Nulla libero reprehenderit praesentium aut amet expedita voluptas exercitationem dolore minus ut. Itaque tempora quam vel unde laboriosam.
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Consequatur autem quae sapiente? Nulla libero reprehenderit praesentium aut amet expedita voluptas exercitationem dolore minus ut. Itaque tempora quam vel unde laboriosam.
.shape-outside-element { width: 8rem; aspect-ratio: 1; background-color: var(--illustration-pink, --pre-pink); float: left; /* mandatory */ margin-right: 2rem; } /* circle shape */ .shape-outside-element:first-child { border-radius: 50%; shape-outside: circle(50%); } /* diamond shape */ .shape-outside-element:nth-of-type(2) { shape-outside: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); /* the property only changes the relationship of adjacent elements, not the geometry of the element itself */ /* to change the geometry of the element we use the clip-path property, with the same values */ clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); } /* ellipse shape */ .shape-outside-element:nth-of-type(3) { shape-outside: ellipse(35% 50% at 50% 50%); clip-path: ellipse(35% 50% at 50% 50%); }
Shape-outside : further resources
Drop Shadow
Like the box-shadow property, the drop-shadow function applies a shadow around an element. But, unlike the former, the shadow is applied to the shape of that element, not to its container box. The drop-shadow function is used with the filter property.
.drop-shadow-element { width: 8rem; aspect-ratio: 1; background-color: var(--illustration-green, --pre-green); /* with drop-shadow, we use the filter property */ filter: drop-shadow(1rem -0.25rem 0.5rem var(--shadow-color)); } .box-shadow-element { width: 8rem; aspect-ratio: 1; background-color: var(--illustration-green, --pre-green); /* the box-shadow is applied to the box container */ box-shadow: 1rem -0.25rem 0.5rem var(--shadow-color); } /* circle shape */ .drop-shadow-circle, .box-shadow-circle { border-radius: 50%; } /* triangle shape (using gradient) */ .drop-shadow-triangle, .box-shadow-triangle { background-color: initial; background-image: linear-gradient(45deg, var(--illustration-green, --pre-green) 50%, rgba(255, 255, 255, 0) 50%); } /* cross shape (using clip-path) */ .drop-shadow-cross, .box-shadow-cross { display: flex; flex-direction: column; width: 8rem; aspect-ratio: 1; background-color: var(--illustration-green, --pre-green); /* With clipped element, we lose the shadow */ clip-path: polygon(20% 0%, 0% 20%, 30% 50%, 0% 80%, 20% 100%, 50% 70%, 80% 100%, 100% 80%, 70% 50%, 100% 20%, 80% 0%, 50% 30%); } .drop-shadow-container { /* Solution : the drop-shadow filter is applied on the parent element */ filter: drop-shadow(1rem -0.25rem 0.5rem var(--shadow-color)); }