Cascading Style Sheets (CSS) is a style sheet language used for describing the presentation of a document written in a markup language like HTML. CSS is the main technology of the World Wide Web, alongside HTML and JavaScript. When two rules conflict, the cascade decides which one wins, and !important overrides it, which is why it should be a last resort.
CSS is designed to enable the separation of presentation and content, including layout, colors, and fonts. This separation can improve content accessibility, provide more flexibility and control in the specification of presentation characteristics, enable multiple web pages to share formatting by specifying the relevant CSS in a separate .css file and reduce complexity and repetition in the structural content. For class names, a convention like BEM naming keeps large stylesheets predictable. To keep your stylesheets readable, label sections with comments.
Did you know that you can create a typing effect with zero JavaScript?
<div class="typing-demo-wrapper">
<div class="typing-demo">
This is a typing demo.
</div>
</div>
.typing-demo-wrapper {
height: 200px;
overflow: auto;
/*This part is important for centering*/
display: grid;
place-items: center;
}
.typing-demo {
width: 22ch;
animation: typing 2s steps(22), blink .5s step-end infinite alternate;
white-space: nowrap;
overflow: hidden;
border-right: 3px solid;
font-family: monospace;
font-size: 2em;
}
@keyframes typing {
from {
width: 0
}
}
@keyframes blink {
50% {
border-color: transparent
}
}
This is a typing demo.
When you work with transparent images you can use `drop-shadow()` filter function to create a shadow on the image’s content, instead of `box-shadow` property which creates a rectangular shadow behind an element’s entire box.
<div class="drop-shadow-wrapper">
<div class="mr-8">
<div class="mb-4 text-center">
box-shadow
</div>
<img class="box-shadow" src="https://markodenic.com/man_working.png" alt="Image with box-shadow">
</div>
<div>
<div class="mb-4 text-center">
drop-shadow
</div>
<img class="drop-shadow" src="https://markodenic.com/man_working.png" alt="Image with drop-shadow">
</div>
</div>
.drop-shadow-wrapper {
height: 350px;
display: flex;
align-items: center;
justify-content: center;
}
.mr-2 {
margin-right: 2em;
}
.mb-1 {
margin-bottom: 1em;
}
.text-center {
text-align: center;
}
.box-shadow {
box-shadow: 2px 4px 8px #585858;
}
.drop-shadow {
filter: drop-shadow(2px 4px 8px #585858);
}
box-shadow
Image with box-shadow
drop-shadow
Image with drop-shadow
Smooth scrolling without JavaScript, with just one line of CSS.
<div class="cp-smooth">
<nav class="cp-smooth__nav">
<a href="#cp-smooth-1">One</a>
<a href="#cp-smooth-2">Two</a>
<a href="#cp-smooth-3">Three</a>
</nav>
<div class="cp-smooth__scroller">
<section id="cp-smooth-1">Section one</section>
<section id="cp-smooth-2">Section two</section>
<section id="cp-smooth-3">Section three</section>
</div>
</div>
.cp-smooth {
font-family: system-ui, sans-serif;
}
.cp-smooth__nav {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.cp-smooth__nav a {
padding: 6px 14px;
border-radius: 8px;
background: #1d4ed8;
color: #fff;
text-decoration: none;
}
.cp-smooth__scroller {
height: 200px;
overflow-y: auto;
border-radius: 12px;
/* The one line that does it */
scroll-behavior: smooth;
}
.cp-smooth__scroller section {
display: grid;
place-items: center;
height: 200px;
color: #fff;
font-size: 24px;
font-weight: 700;
}
#cp-smooth-1 { background: #1e3a8a; }
#cp-smooth-2 { background: #0e7490; }
#cp-smooth-3 { background: #6d28d9; }
Easily center anything, horizontally and vertically, with 3 lines of CSS. Explore other ways to center a div.
.center {
display: flex;
align-items: center;
justify-content: center;
}
Did you know that you can use your own image, or even emoji as a cursor?
<div class="cursors-wrapper">
<div class="tile">
Default
</div>
<div class="tile tile--image-cursor">
Image Cursor
</div>
<div class="tile tile--emoji-cursor">
Emoji Cursor
</div>
</div>
.cursors-wrapper {
display: flex;
height: 350px;
align-items: center;
justify-content: center;
background: #4776e6;
background: -webkit-linear-gradient(to right, #4776e6, #8e54e9);
background: linear-gradient(to right, #4776e6, #8e54e9);
padding: 0 10px;
}
.tile {
width: 200px;
height: 200px;display: flex;
align-items: center;
justify-content: center;
background-color: #de5448;
margin-right: 10px;color: #fff;
font-size: 1.4em;
text-align: center;
}
.tile--image-cursor {
background-color: #1da1f2;
cursor: url(https://picsum.photos/20/20), auto;
}
.tile--emoji-cursor {
background-color: #4267b2;
cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>🚀</text></svg>"), auto;
}
Did you know that you can truncate text with plain CSS?
It takes three properties together: overflow: hidden, white-space: nowrap and text-overflow: ellipsis. The element also needs a limited width, either set directly or from a flex or grid parent.
<div class="truncate-wrapper">
<div class="overflow-ellipsis">
This text is truncated, because there is not enough space to display it completely.
</div>
</div>
.truncate-wrapper {
display: flex;
height: 250px;
align-items: center;
justify-content: center;
background: #4776e6;
background: -webkit-linear-gradient(to right, #4776e6, #8e54e9);
background: linear-gradient(to right, #4776e6, #8e54e9);
}
.overflow-ellipsis {
width: 200px;
background-color: #fff;
padding: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
This text is truncated, because there is not enough space to display it completely.
You can use -webkit-line-clamp property to truncate the text to the specific number of lines. An ellipsis will be shown at the point where the text is clamped.
Use it together with display: -webkit-box, -webkit-box-orient: vertical and overflow: hidden. Despite the prefixes, this combination works in all modern browsers and is the standard way to show a two- or three-line preview with an ellipsis. Full guide with demos: text-overflow: ellipsis and line-clamp.
This text is trimmed to 3 lines
<div class="truncate-tile">
<div class="line-clamp">
You can use <code>-webkit-line-clamp</code> property to truncate the text to the specific number of lines.
An ellipsis will be shown at the point where the text is clamped.
</div>
</div>
This text is trimmed to 4 lines
<div class="truncate-tile">
<div class="line-clamp line-clamp--four">
You can use <code>-webkit-line-clamp</code> property to truncate the text to the specific number of lines.
An ellipsis will be shown at the point where the text is clamped.
</div>
</div>
This text is not trimmed
<div class="truncate-tile">
<div>
You can use <code>-webkit-line-clamp</code> property to truncate the text to the specific number of lines.
An ellipsis will be shown at the point where the text is clamped.
</div>
</div>
.truncate-tile {
background: linear-gradient(to right, #2B32B2, #1488CC);
padding: 15px;
margin-bottom: 15px;
padding: 15px;
width: 300px;
color: #fff;
}
.line-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* Change this line if you want. In this case it trimmed the text to 3 lines. */
overflow: hidden;
}
.line-clamp--four {
-webkit-line-clamp: 4; /* Trimmed the second tile to four lines. */
}
This text is trimmed to 3 lines
You can use -webkit-line-clamp property to truncate the text to the specific number of lines. An ellipsis will be shown at the point where the text is clamped.
This text is trimmed to 4 lines
You can use -webkit-line-clamp property to truncate the text to the specific number of lines. An ellipsis will be shown at the point where the text is clamped.
This text is not trimmed
You can use -webkit-line-clamp property to truncate the text to the specific number of lines. An ellipsis will be shown at the point where the text is clamped.
The `::selection` CSS pseudo-element applies styles to the part of a document that has been highlighted by the user (such as clicking and dragging the mouse across text). It works like the better-known ::before and ::after, which add content without extra HTML.
<p class="cp-selection">Select this text with your mouse to see the custom highlight colors.</p>
.cp-selection {
padding: 24px;
border-radius: 12px;
background: #f1f5f9;
color: #0f172a;
font: 20px/1.5 system-ui, sans-serif;
}
.cp-selection::selection {
background: #1d4ed8;
color: #fff;
}
Select this text with your mouse to see the custom highlight colors.
Did you know that you can make any element resizable, just like `<textarea>`?
It only works when overflow is something other than visible, for example overflow: auto.
.resize {
resize: both;
}
You can use the `:target` pseudo-class to create modals with zero JavaScript. A hidden checkbox does the same job for a CSS-only hamburger menu.
<div class="cp-modal-demo">
<a class="cp-modal-demo__open" href="#cp-modal">Open modal</a>
</div>
<div class="cp-modal" id="cp-modal">
<div class="cp-modal__box">
<h4>Hello from a CSS modal</h4>
<p>No JavaScript: the :target pseudo-class shows it when the URL ends in #cp-modal.</p>
<a class="cp-modal__close" href="#css-modals">Close</a>
</div>
</div>
.cp-modal-demo {
display: grid;
place-items: center;
padding: 32px;
border-radius: 12px;
background: #f1f5f9;
font-family: system-ui, sans-serif;
}
.cp-modal-demo__open,
.cp-modal__close {
padding: 10px 18px;
border-radius: 8px;
background: #1d4ed8;
color: #fff;
text-decoration: none;
}
.cp-modal {
position: fixed;
inset: 0;
z-index: 1000;
display: none;
place-items: center;
padding: 16px;
background: rgba(15, 23, 42, 0.6);
font-family: system-ui, sans-serif;
}
/* Visible only while the URL hash matches its id */
.cp-modal:target {
display: grid;
}
.cp-modal__box {
max-width: 360px;
padding: 24px;
border-radius: 14px;
background: #fff;
color: #0f172a;
}
.cp-modal__box h4 {
margin: 0 0 8px;
}
.cp-modal__box p {
margin: 0 0 20px;
}
The `calc()` CSS function lets you perform calculations when specifying CSS property values:
You can mix units in one expression: calc(100% - 2rem) gives a full-width element with side margins, and calc(100vh - 64px) fills the screen below a 64px header. Put spaces around + and -, or the expression is invalid. For fluid sizes with limits, combine it with clamp() (see the tip below).
.calculated-width: {
width: calc(100% - 30px);
}
You can use the `:empty` selector to style an element that has no children or text at all:
<div class="cp-empty">
<div>Has text</div>
<div></div>
<div>Has text</div>
</div>
.cp-empty {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
font-family: system-ui, sans-serif;
}
.cp-empty div {
display: grid;
place-items: center;
height: 90px;
border-radius: 12px;
background: #dbeafe;
color: #1e3a8a;
}
/* No children and no text, not even a space */
.cp-empty div:empty {
background: repeating-linear-gradient(45deg, #fee2e2 0 10px, #fecaca 10px 20px);
}
Use the standard scrollbar-color and scrollbar-width properties for Chrome, Edge and Firefox, and the ::-webkit-scrollbar pseudo-elements for Safari. To hide the scrollbar but keep the box scrollable, set scrollbar-width: none.
<div class="cp-scrollbar">
<p>Scroll this box to see the custom scrollbar.</p>
<p>Chrome, Edge and Firefox use the standard scrollbar-color and scrollbar-width properties.</p>
<p>Safari uses the ::-webkit-scrollbar pseudo-elements, so both are included.</p>
<p>Keep the thumb contrasting with the track, so the scrollbar stays easy to find.</p>
<p>That's it.</p>
</div>
.cp-scrollbar {
height: 160px;
overflow-y: auto;
padding: 0 20px;
border-radius: 12px;
background: #f1f5f9;
color: #0f172a;
font: 18px/1.6 system-ui, sans-serif;
/* Standard: thumb color, then track color */
scrollbar-color: #1d4ed8 #dbeafe;
scrollbar-width: thin;
}
/* Safari */
.cp-scrollbar::-webkit-scrollbar {
width: 10px;
}
.cp-scrollbar::-webkit-scrollbar-track {
background: #dbeafe;
}
.cp-scrollbar::-webkit-scrollbar-thumb {
border-radius: 10px;
background: #1d4ed8;
}
You can create sticky section headers with 2 lines of CSS. Not sure how sticky differs from absolute, relative and fixed? See position absolute vs relative.
A sticky element needs a threshold like top: 0, and it only sticks inside its parent. If it doesn’t stick, check the ancestors: overflow: hidden or overflow: auto on any parent creates a new scroll container and breaks it.
<div class="cp-sticky">
<section>
<h4>Section 1</h4>
<p>Scroll inside this box. Each header sticks to the top until the next section pushes it away.</p>
</section>
<section>
<h4>Section 2</h4>
<p>The sticky header stays within its own section, its containing block.</p>
</section>
<section>
<h4>Section 3</h4>
<p>Two lines of CSS: position: sticky and top: 0.</p>
</section>
</div>
.cp-sticky {
height: 220px;
overflow-y: auto;
border-radius: 12px;
background: #f1f5f9;
color: #0f172a;
font-family: system-ui, sans-serif;
}
.cp-sticky h4 {
position: sticky;
top: 0;
margin: 0;
padding: 12px 16px;
background: #1d4ed8;
color: #fff;
}
.cp-sticky p {
margin: 0;
padding: 16px 16px 120px;
font-size: 17px;
}
Scroll inside this box. Each header sticks to the top until the next section pushes it away.
The sticky header stays within its own section, its containing block.
Two lines of CSS: position: sticky and top: 0.
You can use the CSS Scroll Snap feature to create well-controlled scroll experiences:
<div class="cp-snap">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</div>
.cp-snap {
display: flex;
gap: 12px;
overflow-x: auto;
padding-bottom: 8px;
scroll-snap-type: x mandatory;
}
.cp-snap div {
display: grid;
place-items: center;
flex: 0 0 75%;
height: 160px;
border-radius: 12px;
background: linear-gradient(135deg, #1d4ed8, #0e7490);
color: #fff;
font: 700 40px system-ui, sans-serif;
scroll-snap-align: center;
}
Create dynamic CSS-only tooltips, using the attr() CSS function.
attr() reads any attribute of the element, so each tooltip’s text lives in the HTML and one CSS rule styles them all. Attribute selectors work the same way: a[target=”_blank”]::after can add an icon to links that open in a new tab.
<p class="cp-tooltips">
Hover over <span data-tooltip="I come from the data-tooltip attribute">this</span>
or <span data-tooltip="attr() reads any attribute">that</span>.
</p>
.cp-tooltips {
padding: 56px 24px 24px;
border-radius: 12px;
background: #f1f5f9;
color: #0f172a;
font: 20px system-ui, sans-serif;
text-align: center;
}
.cp-tooltips [data-tooltip] {
position: relative;
border-bottom: 2px dashed #1d4ed8;
cursor: help;
}
.cp-tooltips [data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 8px);
left: 50%;
transform: translateX(-50%);
width: max-content;
max-width: 220px;
padding: 6px 10px;
border-radius: 6px;
background: #0f172a;
color: #fff;
font-size: 14px;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
}
.cp-tooltips [data-tooltip]:hover::after {
opacity: 1;
}
Hover over this or that.
You can change the color of the text input cursor.
Caret Color example codepen preview.
Use the :in-range and :out-of-range pseudo-classes to style number inputs whose value is inside or outside the min and max attributes.
/* <input type="number" min="1" max="10"> */
input:in-range {
border-color: #16a34a;
}
input:out-of-range {
border-color: #dc2626;
}
Input pseudo-classes `::in-range` and `out-of-range`
Use the background-clip property to create beautiful headlines.
Design the gradient in the CSS gradient generator, or add an outline with CSS text stroke.
<div class="cp-fancy">
<h4>Fancy headline</h4>
</div>
.cp-fancy {
padding: 24px;
border-radius: 12px;
background: #0f172a;
text-align: center;
}
.cp-fancy h4 {
margin: 0;
font: 900 clamp(36px, 8vw, 64px) system-ui, sans-serif;
background: linear-gradient(90deg, #22d3ee, #6366f1, #a855f7);
/* Paint the background only where the text is */
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
Use the gap CSS property to set the gaps (gutters) between rows and columns.
gap works in both flexbox and grid, and replaces the old margin hacks between items.
Flex Gap Property
Use the grayscale() filter function to convert the input image to grayscale.
grayscale-function
The gradient sits on the border-box and a solid fill on the padding-box, so the corners stay rounded. Design the colors in the CSS gradient generator, or see the animated border snippet for a moving version. Both tricks work with radial gradients too.
<div class="cp-gradient-border">Rounded gradient border</div>
.cp-gradient-border {
display: grid;
place-items: center;
height: 140px;
border: 4px solid transparent;
border-radius: 16px;
/* Inner fill on the padding box, gradient on the border box */
background:
linear-gradient(#fff, #fff) padding-box,
linear-gradient(135deg, #22d3ee, #6366f1, #a855f7) border-box;
color: #0f172a;
font: 600 20px system-ui, sans-serif;
}
:has() selects an element based on what it contains, the parent selector CSS was missing for years. It works in all modern browsers.
/* A card that contains an image */
.card:has(img) {
padding: 0;
}
/* A label followed by an invalid input */
label:has(+ input:invalid) {
color: #dc2626;
}
Keep an element’s proportions without the old padding-top hack. Great for videos, thumbnails and avatars.
.video {
width: 100%;
aspect-ratio: 16 / 9;
}
.avatar {
width: 64px;
aspect-ratio: 1;
object-fit: cover;
}
Recolor native checkboxes, radio buttons, range sliders and progress bars with one line, no custom styling needed.
:root {
accent-color: #1d4ed8;
}
clamp(min, preferred, max) lets text grow with the screen but never get too small or too large. No media queries needed.
h1 {
/* 2rem on phones, up to 4rem on wide screens */
font-size: clamp(2rem, 1rem + 4vw, 4rem);
}
Happy coding!
Want to explore more? Here’s a tutorial on how to create CSS Pulse Animation.
Yes. Every tip on this page works in current Chrome, Edge, Firefox and Safari. Where a property still needs a prefix, like -webkit-line-clamp, the prefixed version is supported everywhere.
Yes. The typing effect, smooth scrolling, modals, tooltips, sticky headers and scroll snap on this page are all pure CSS.
Centering with display: grid; place-items: center, and truncating text with text-overflow: ellipsis. Both solve problems you hit on almost every project.