Secrets
Vendor prefixes
Here are some helpful links to determin if you need an autoprefixer:
Tips
currentColor
Considered the first ever variable in CSS, this value is equal to the color
property. The color
property is inherited. This means that it applies either the color
value for the current element or–if color
is not defined–the initial color for that property.
(avoid) media queries
Now, you can use container queries.
Try to avoid media queries, when possible. You want to exhaust every possible way to make the website flexible without resorting to media queries. If you do use them, let the content dictate the breakpoints, not an arbitrary device.
Here are some tips to avoid them:
- Use percentages instead of fixed widths
- Use
max-width
instead ofwidth
so you can adapt to smaller viewports - Always use
max-width: 100%;
for replaced content such asimg
,object
,video
, andiframe
. Replaced elements are elements that are replaced by resources that the browser fetches. - Use
column-width
instead ofcolumn-count
so you can get one column on smaller resolutions.
Use shorthands
Longhand properties only apply to that specific property–you don’t reset all the other properties that might affect your styles. For example:
body {
background: red; /* background will always be red */
background-color: red; /* other background-* props might affect the background */
}
You can also combine shorthand and longhand declarations to reduce the amount of edits. Here, we use the shorthand for the url()
fallbacks, and the specific properties for specific styles:
body {
background: url(some.png) no-repeat top right,
url(some.png) no-repeat bottom right,
url(some.png) no-repeat bottom left;
background-size: 2em 2em;
background-repeat: no-repeat;
}
In the preceding example, you can add all the fallbacks to the background
property, and add the individual styles to the other props. If you need to make an edit to *-size
or *-repeat
, then you edit only those properties.
Backgrounds and borders
No scroll background
This ruleset makes the background stick in the same spot, even if you scroll past it:
body {
background: url(/assets/Green_Grass.JPG);
background-size: cover;
background-attachment: fixed;
}
Translucent borders
By default, borders extend underneath the border area, so it is hard to see transparent borders on elements with lighter backgrounds. To fix this, use the background-clip
property to clip the background at the padding-box
, rather than the border-box
:
.translucent-border {
border: 20px solid hsla(0, 0%, 100%, 0.5);
background: white;
background-clip: padding-box;
}
Multiple borders
The original fix was to use multiple box-shadow
properties, but that is bad for multiple reasons:
- Shadows don’t take up layout space, so you have to add additional margin to make the doc flow correctly
- Shadows can’t capture mouse events
The fix is to use outline
:
.multiple-border {
background: yellowgreen;
border: 10px solid #655;
outline: 5px solid deeppink;
}
You can also add outline-offset
to add space between the border and the outline. Here, we use a negative value to make the outline look like stitching:
.inner {
background: yellowgreen;
border: 10px solid #655;
outline: 1px dashed deeppink;
outline-offset: -24px;
}
Flexible background positioning
When you need to position an image within a container, you often need to account for padding. For example, if you want to position an image in the bottom right of the container, you can explicitly enter the padding values in background-position
:
.background-img {
padding: 10px;
background: url(/assets/cancel-icon.svg) no-repeat yellowgreen;
background-position: right 20px bottom 10px; /* 20px from right, 10px from bottom */
}
This is difficult in case you want to change the padding. Instead, use the calc()
function to position the image from the top left corner of the container (100%
), and subtract the padding. It’s probably easier to use variables:
.background-img {
padding: 10px;
background: url(/assets/cancel-icon.svg) no-repeat yellowgreen;
background-position: calc(100% - 20px) calc(100% - 10px);
}
Striped background
Create stripes by assigning equal or complementary (stop% + stop% = 100%) color stops to each color in repeating- linear-gradient()
. You can control the height or width of the stripes by adding start and stop points after the stripe color.
/* #5a 30px from the start of each gradient cycle */
.single-val {
background: repeating-linear-gradient(#fb3, #58a 30px);
}
/* start #fb3 at 0 and end at 15px, start #58a at 15px and end at 30px */
.double-val {
background: repeating-linear-gradient(#fb3 0 15px, #58a 0 30px);
}
/* horizontal */
.h-stripe {
background: repeating-linear-gradient(
#fb3 10% 20%,
#58a 20% 30%,
red 30% 40%
);
}
/* vertical */
.v-stripes {
background: repeating-linear-gradient(
to right,
#fb3 10% 20%,
#58a 20% 30%,
red 30% 40%
);
}
If you want to simplify this, you can set a background
color, then use background-image
to create a semi-transparent white stripe and a completely transparent stripe. The semi-transparent white stipe is because you generally don’t want to have stripes with starkly contrasting colors–you want it to be just a bit lighter:
.nice-stripes {
background: var(--background-clr);
background-image: repeating-linear-gradient(
30deg,
hsla(0, 0%, 100%, 0.1) 0 15px,
transparent 0 30px
);
}
Complex background patterns
For repeating patterns, use one of the following strategies:
linear-gradient
andbackground-size
. This seems more maintainable, because you specify the stops with%
, and then you can play with the size of the pattern repeating with thebackground-size
property.repeating-linear-gradient
and stops. To change the pattern, you have to set multiple values in the stops.
Tablecloth
You can either specify the size of the pattern with linear-gradient
and background-size
, or you can calculate it with the color stops and repeating-linear-gradient
. Both of these rules create a tablecloth-like grid:
/* each gradient uses 50% of the background-size */
.tablecloth {
background: white;
background-image: linear-gradient(
90deg,
rgba(200, 0, 0, 0.5) 50%,
transparent 0
),
linear-gradient(
rgba(200, 0, 0, 0.5) 50%,
transparent 0);
background-size: 30px 30px; /* width height */
}
.tablecloth-repeating {
background: white;
background-image: repeating-linear-gradient(
rgba(200, 0, 0, 0.5) 0 15px,
transparent 15px 30px
),
repeating-linear-gradient(
90deg,
rgba(200, 0, 0, 0.5) 0 15px,
transparent 0px 30px
);
}
Grid
These styles create a grid that looks like drafting paper:
.grid {
background: #58a;
background-image: linear-gradient(white 1px, transparent 0),
linear-gradient(90deg, white 1px, transparent 0);
background-size: 30px 30px;
}
.grid-repeating {
background: #58a;
background-image: repeating-linear-gradient(
white 0 1px,
transparent 0 30px),
repeating-linear-gradient(
90deg,
white 0 1px,
transparent 0 30px);
}
Nested grid
This pattern makes it look like real drafting paper, where each square has smaller grids within. The background-image
and background-size
values map to each other in the order they are listed:
.drafting-paper {
background: #58a;
background-image:
linear-gradient(white 2px, transparent 0),
linear-gradient(90deg, white 2px, transparent 0),
linear-gradient(hsla(0, 0%, 100%, 0.3) 1px, transparent 0),
linear-gradient(90deg, hsla(0, 0%, 100%, 0.3) 1px, transparent 0);
background-size: 75px 75px, 75px 75px,
15px 15px, 15px 15px;
}
Horizontal stripes
This pattern looks like lines on a piece of paper:
.notebook {
background-image: linear-gradient(white 1px, transparent 0);
background-size: 30px 30px;
}
Polka dot
Add polka dots with radial-gradient()
. Here, there are 2 different patterns. The first applies only one radial-gradient
, and the second applies two for more polka dots.
The background-position
of the second example applies different positioning for each background-image
gradient. To make this work, the second set of values must be half the size of the background-size
:
.polka-dots {
background: #655;
background-image: radial-gradient(tan 30%, transparent 0);
background-size: 30px 30px;
}
.double-dots {
background: #655;
background-image:
radial-gradient(tan 30%, transparent 0),
radial-gradient(tan 30%, transparent 0);
background-size: 30px 30px;
background-position: 0 0, 15px 15px;
}
Changes to this require a lot of different edits, so you can create a @mixin
in SASS:
@mixin double-dot($size, $dot, $base, $accent) {
background: $base;
background-image:
radial-gradient($accent $dot, transparent 0),
radial-gradient($accent $dot, transparent 0);
background-size: $size $size;
background-position: 0 0, $size/2 $size/2;
}
.container {
@include double-dot(30px, 30%, #655, tan);
}
Checkerboard
Here is a basic checkerboard:
.checker-bg {
background: #eee;
background-image:
linear-gradient(45deg, #bbb 25%, transparent 0),
linear-gradient(45deg, transparent 75%, #bbb 0),
linear-gradient(45deg, #bbb 25%, transparent 0),
linear-gradient(45deg, transparent 75%, #bbb 0);
background-position: 0 0, 15px 15px, 15px 15px, 30px 30px;
background-size: 30px 30px;
}
You can also simplify things and just embed and SVG:
.svg {
background: #eee
url('data:image/svg+xml, \
<svg xmlns="http://www.w3.org/2000/svg" \
width="100" height="100" fill-opacity=".25"> \
<rect x="50" width="50" height="50" /> \
<rect y="50" width="50" height="50" /> \
</svg>');
background-size: 30px 30px;
}
Continuous image borders
Set an image as the border with backgound-clip
and background-origin
. Apply two linear backgrounds to the element: the first is the background that looks like a nested div because its a linear gradient of the same colors, and the second is the image. Set the background-clip
to padding-box
on the first, and set it to border-box
on the second.
Set the background-origin
to border-box
so it doesn’t repeat within the image:
.image-border {
padding: 1em;
border: 3em solid transparent;
background: linear-gradient(white, white), url(/assets/background.jpg);
background-size: cover;
background-clip: padding-box, border-box;
background-origin: border-box;
}
Vintage envelope
This reuses the background border idea but creates a striped border with repeating-linear-gradient
:
padding: 1em;
border: 1em solid transparent;
background: linear-gradient(white, white) padding-box,
repeating-linear-gradient(
-45deg,
red 0,
red 12.5%,
transparent 0,
transparent 25%,
#58a 0,
#58a 37.5%,
transparent 0,
transparent 50%
)
0 / 5em 5em;
}
Shapes
Flexible ellipses
border-radius
accepts values in multiple formats:
50% / 50%
: horizontal and vertical radiia b c d
: corners beginning at top-left. If you provide less than four values, the values are doubled–c
is applied toc
andd
, two values meansa
is applied toa
andc
andb
is applied tob
andd
.a b c d / v x y z
: horizonal and vertical radii for all four corners
Make sure you understand which
.half-ellipses {
background: #fb3;
width: 200px;
height: 300px;
border-radius: 50% / 100% 100% 0 0;
}
.left-ellispses {
background: #fb3;
width: 200px;
height: 300px;
border-radius: 100% 0 0 100% / 50%;
}
.quarter-ellipses {
background: #fb3;
width: 200px;
height: 300px;
border-radius: 100% 0 0 0;
}
Diamond images
Do this with clip-path
. Clipping paths lets you clip the element into the shape that we want.
This also includes a hover pseudo-class that reveals the entire image on hover:
.picture {
width: 400px;
overflow: hidden;
}
.picture > img {
max-width: 100%;
clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%);
transition: clip-path 300ms ease-in-out;
}
.picture > img:hover {
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
}
Cutout corners
The easiest way to do this is with clip-path
.
.picture {
padding: 20rem 30rem;
background: #58a;
clip-path: polygon(
20px 0,
calc(100% - 20px) 0,
100% 20px,
100% calc(100% - 20px),
calc(100% - 20px) 100%,
20px 100%,
0 calc(100% - 20px),
0 20px
);
}
Pie charts
Use conic-gradient
:
.pie {
width: 100px;
height: 100px;
background: yellowgreen;
border-radius: 50%;
background-image: conic-gradient(
deeppink 20%,
#fb3 0,
#fb3 30%,
yellowgreen 0
);
}
Visual effects
Shadows
Passing positive values to box-shadow
can offset the shadow to the right and bottom. Negatives do the opposite. The last value you add is how much blur you want on the shadow. The shadow also respects the border-radius
value.
If you want to increase the size of the shadow, add the fourth value before the color, the spread radius.
Duplicate offset (adjacent sides)
This creates a duplicate of the element that is offset by 50px
:
.box {
box-shadow: 50px 50px 0 20px black;
}
Shadow on one side
Use the spread radius here, and make one of the offset values 0
. Put the shadow on either side with a positive or negative spread radius:
.box {
box-shadow: 0 50px 0 -20px black;
}
Border/outline shadow
box-shadow
works only on HTML elements. If you want to include a pseudo-element or a decoration like an outline, you have to use filter: drop-shadow();
.
This takes an x-offset, y-offset, blur value, and color. This example creates a duplicate of the box and the outline offset:
.box {
filter: drop-shadow(50px 50px 0 black);
}
Frosted glass
If you have text that you need to display over a background, apply a backdrop filter to the container element:
main {
backdrop-filter: blur(10px);
}
Folded corner effect
Apply these styles to a div:
.note {
height: 20rem;
width: 30rem;
// background: yellowgreen;
position: relative;
background: linear-gradient(-150deg, transparent 1.5em, yellowgreen 0);
border-radius: 0.5em;
}
.note::before {
content: "";
position: absolute;
top: 0;
right: 0;
background: linear-gradient(
to left bottom,
transparent 50%,
rgba(0, 0, 0, 0.2) 0,
rgba(0, 0, 0, 0.4)
)
100% 0 no-repeat;
width: 1.73em;
height: 3em;
transform: translateY(-1.3em) rotate(-30deg);
transform-origin: bottom right;
border-bottom-left-radius: inherit;
box-shadow: -0.2em 0.2em 0.3em -0.1em rgba(0, 0, 0, 0.15);
}
Typography
Custom underlines
Here are some properties that you can manipulate to manipulate text underlines:
.underline {
text-decoration: underline;
text-decoration-style: dashed;
text-underline-position: auto;
text-decoration-thickness: 3px;
text-underline-offset: 2px;
}
Letterpress
This effect is supposed to give the impression of text pressed into the page. This works so long as the text is not completely black, and the background is not completely white. You need a dark text on a lighter background:
.content {
background: hsl(210, 13%, 60%);
color: hsl(210, 13%, 30%);
text-shadow: 0 1px 1px hsla(0, 0%, 100%, 0.8);
}
Glowing text
Use the blur value in text-shadow
with some good colors to create a glowing text effect:
.glow {
background: #203;
color: #ffc;
text-shadow: 0 0 0.1em, 0 0 0.3em;
}
Here it is as a hover state:
.glow:hover {
color: transparent;
text-shadow: 0 0 0.1em white, 0 0 0.3em white;
}
Extruding text
This effect makes the text look 3D. You create it by applying multiple text-shadow
properties with increasing x- and y-offset values:
.box {
background: #58a;
color: #ffc;
text-shadow: 1px 1px black,
2px 2px black,
3px 3px black,
4px 4px black,
5px 5px black,
6px 6px black,
7px 7px black,
8px 8px black;
}
Structure and Layout
Sticky footer
Flexbox makes the footer stick to the bottom. Make the <body>
a flex container using the column
layout that uses at least 100vh
of the viewport height, and then make whichever element contains your content (here, it is <main>
) use flex: 1;
. This makes the element use all available space:
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
}
Transitions and animations
Blinking
This might be a bad idea for people with reduced motion, but you can make an element blink with a keyframe that changes the background
to transparent
.
Here is the keyframe:
```css
@keyframes blink {
50% {
background: transparent;
}
}
This animation makes the circle blink one time per second for three seconds:
.blinker {
animation: 1s blink 3 steps(1);
}
This is more basic, and makes the keyframe blink six times per second for three seconds:
.blinker {
animation: 1s blink 6;
}
This makes the circle blink forever:
.box {
animation: 1s blink infinite;
}
Typing simulation
This solution has the following requirements and restrictions:
- It will not work for a multi-line output
- It requires you specify the number of characters, so its not reusable
This makes text appear one character at a time, like when you want to simulate a terminal entering a prompt. We will animate a <p>
element with two keyframes.
First, define the keyframes. The typing
keyframes controls the width of the element. The idea is that the <p>
element’s width will increase from 0 to n characters:
@keyframes typing {
from {
width: 0;
}
}
Next, we create an animation for the blinking cursor. We will use the border-right
property so the cursor follows the expanding element. For example, if we used border-left
, the cursor would just blink at the left of the <p>
tag for the duration of the animation.
Here, we create an animation that makes the border transparent:
@keyframes caret {
50% {
border-color: transparent;
}
}
Finally, define the styles for the text:
- set
width
equal to the character count of the text + 1. The additional character lets the cursor blink in the space directly after the text. - don’t allow text wrapping. Otherwise, the element will wrap within its current width
- hide the overflow. This is what makes the text look like it is appearing on the screen
- make the right border as wide as a single character
- apply the animations.
typing
steps should equal thewidth
value, andcaret
should begin aftertyping
concludes (2s
) and continue infinitely.
.terminal > p {
width: 24ch;
white-space: nowrap;
overflow: hidden;
border-right: 1ch solid;
animation: typing 2s steps(24), caret 1s linear 2s infinite;
}