2D TRANSFORMS
2D transforms change how an element looks in a two-dimensional space.
They do not describe how many states an effect has. They describe what kind of visual change happens.
- transition = how a change moves from one state to another
- animation = how a change moves through many keyframes
- transform = move, rotate, scale, or skew the element
Main Idea
Transforms are usually great for UI effects because they change the visual result without rebuilding the whole layout around the element.
transform can:
translate() -> move
rotate() -> turn
scale() -> resize visually
skew() -> tilt
By default, a transform uses the center of the element as its transform origin.
Syntax
transform: none;
transform: scale(1.2);
transform: translateX(20px);
transform: rotate(45deg);
transform: skew(20deg, 10deg);
transform: translateY(-10px) scale(1.05) rotate(2deg);
If the value is not none, the element is a transformed element.
Important Rule
A transform changes the visual appearance of an element, but it usually does not push nearby elements around.
change width/height -> layout may move
change transform -> visual result changes
That is why transform and opacity are often the best properties for smooth animation.
Scale
Scaling changes the size of an element.
transform: scale(1.15);
This means the element becomes 115% of its original size.
scale(1)= normal sizescale(0.75)= smallerscale(1.25)= biggerscaleX(1.5)= widerscaleY(1.5)= taller
scale(1) -> [ box ]
scale(1.2) -> [ bigger box ]
scale(0.8) -> [ smaller box ]
If scale() gets only one value, the second value is the same:
transform: scale(1.15);
transform: scale(1.15, 1.15);
Scale Example 1: Animated Comparison
This example compares three states: bigger, normal, and smaller.
Code:
<!-- HTML -->
<div class="scale-animation-demo-wrapper">
<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
</div>
<!-- CSS -->
<style>
.scale-animation-demo-wrapper .container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
padding: 20px;
box-sizing: border-box;
}
.scale-animation-demo-wrapper .box {
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
margin-left: 15px;
margin-right: 15px;
box-shadow:
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);
}
.scale-animation-demo-wrapper .box::before {
display: inline-block;
font-size: 20px;
}
.scale-animation-demo-wrapper .box:nth-child(1) {
background-color: orange;
animation: scaleAnimate125 3000ms infinite 1000ms;
}
.scale-animation-demo-wrapper .box:nth-child(1)::before {
content: "scale(1.25)";
}
.scale-animation-demo-wrapper .box:nth-child(2) {
background-color: #03a9f4;
}
.scale-animation-demo-wrapper .box:nth-child(2)::before {
content: "scale(1)";
}
.scale-animation-demo-wrapper .box:nth-child(3) {
background-color: palevioletred;
animation: scaleAnimate75 3000ms infinite 1000ms;
}
.scale-animation-demo-wrapper .box:nth-child(3)::before {
content: "scale(0.75)";
}
@keyframes scaleAnimate125 {
0% {
transform: scale(1);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
@keyframes scaleAnimate75 {
0% {
transform: scale(1);
}
50% {
transform: scale(0.75);
}
100% {
transform: scale(1);
}
}
@media (max-width: 600px) {
.scale-animation-demo-wrapper .box {
width: 120px;
height: 120px;
margin: 10px;
}
.scale-animation-demo-wrapper .box::before {
font-size: 16px;
}
}
</style>
<!-- JavaScript -->
<script>
/* No JavaScript is needed for this animation */
</script>
Scale Example 2: Hover Cards
This is a practical hover effect. Each card grows visually, but the layout is still easy to read.
-
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident temporibus neque, ad debitis magni?
-
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident temporibus neque, ad debitis magni?
-
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident temporibus neque, ad debitis magni?
Code:
<!-- HTML -->
<div class="cat-list-demo-wrapper">
<ul class="cat-list">
<li class="cat-list-item">
<img
src="https://cdn.pixabay.com/photo/2020/11/26/11/48/cat-5778777_1280.jpg"
alt="cat"
/>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident
temporibus neque, ad debitis magni?
</p>
</li>
<li class="cat-list-item">
<img
src="https://cdn.pixabay.com/photo/2023/03/27/14/18/british-shorthair-7880879_1280.jpg"
alt="cat"
/>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident
temporibus neque, ad debitis magni?
</p>
</li>
<li class="cat-list-item">
<img
src="https://cdn.pixabay.com/photo/2023/04/07/07/14/cat-7905702_1280.jpg"
alt="cat"
/>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident
temporibus neque, ad debitis magni?
</p>
</li>
</ul>
</div>
<!-- CSS -->
<style>
.cat-list-demo-wrapper {
padding: 20px;
box-sizing: border-box;
font-family: sans-serif;
}
.cat-list-demo-wrapper .cat-list {
padding: 20px;
margin: 0;
list-style: none;
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.cat-list-demo-wrapper .cat-list-item {
border: 1px solid #212121;
border-radius: 4px;
padding: 4px;
box-sizing: border-box;
flex: 1 1 300px;
transition: transform 250ms ease-in-out;
}
.cat-list-demo-wrapper .cat-list-item:hover {
transform: scale(1.1);
}
.cat-list-demo-wrapper .cat-list-item p {
margin-top: 8px;
margin-bottom: 0;
}
.cat-list-demo-wrapper .cat-list-item img {
display: block;
max-width: 100%;
width: 100%;
height: auto;
}
</style>
<!-- JavaScript -->
<script>
/* No JavaScript needed */
</script>
Translate
Translation moves an element on the X axis, the Y axis, or both.
transform: translateX(50px);
transform: translateY(30px);
transform: translate(-50px, -100px);
X axis: left <------> right
Y axis: up
|
v
down
Percentages in translate() are based on the element itself, not on its parent.
Translate Example 1: Small Hover Movement
This example uses translateX(6px) to give each line a small interactive movement.
- Lorem ipsum dolor sit amet consectetur adipisicing elit
- Lorem ipsum dolor sit amet consectetur adipisicing elit
- Lorem ipsum dolor sit amet consectetur adipisicing elit
Code:
<!-- HTML -->
<div class="tabs-demo-wrapper">
<ul class="tabs">
<li class="pane">
Lorem ipsum dolor sit amet consectetur adipisicing elit
<svg width="32" height="32" viewBox="0 0 20 20" class="icon">
<g><path d="M15 10l-9 5V5l9 5z"></path></g>
</svg>
</li>
<li class="pane">
Lorem ipsum dolor sit amet consectetur adipisicing elit
<svg width="32" height="32" viewBox="0 0 20 20" class="icon">
<g><path d="M15 10l-9 5V5l9 5z"></path></g>
</svg>
</li>
<li class="pane">
Lorem ipsum dolor sit amet consectetur adipisicing elit
<svg width="32" height="32" viewBox="0 0 20 20" class="icon">
<g><path d="M15 10l-9 5V5l9 5z"></path></g>
</svg>
</li>
</ul>
</div>
<!-- CSS -->
<style>
.tabs-demo-wrapper {
box-sizing: border-box;
padding: 20px;
font-family: sans-serif;
}
.tabs-demo-wrapper .tabs {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0;
margin: 0;
list-style: none;
}
.tabs-demo-wrapper .pane {
display: inline-flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 10px 12px;
border: 1px solid #212121;
border-radius: 4px;
max-width: 440px;
cursor: pointer;
box-sizing: border-box;
transition: background-color 0.3s ease, color 0.3s ease, transform 0.3s ease;
}
.tabs-demo-wrapper .pane:hover,
.tabs-demo-wrapper .pane.active {
background-color: #f3f3f3;
transform: translateX(6px);
}
.tabs-demo-wrapper .icon {
flex-shrink: 0;
fill: currentColor;
}
</style>
<!-- JavaScript -->
<script>
const panes = document.querySelectorAll(".tabs-demo-wrapper .pane");
panes.forEach(function (pane) {
pane.addEventListener("click", function () {
pane.classList.toggle("active");
});
});
</script>
Translate Example 2: Axis Comparison
This live example compares movement on the X axis, the Y axis, and both axes together.
Code:
<!-- HTML -->
<div class="translate-animation-demo-wrapper">
<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
</div>
<!-- CSS -->
<style>
.translate-animation-demo-wrapper .container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
padding: 20px;
box-sizing: border-box;
overflow: hidden;
}
.translate-animation-demo-wrapper .box {
width: 200px;
height: 125px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
margin-left: 5px;
margin-right: 5px;
box-shadow:
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);
}
.translate-animation-demo-wrapper .box::before {
font-size: 16px;
}
.translate-animation-demo-wrapper .box:nth-child(1) {
animation: translateAnimateX50 3000ms infinite 1000ms;
background-color: orange;
}
.translate-animation-demo-wrapper .box:nth-child(1)::before {
content: "translateX(50px)";
}
.translate-animation-demo-wrapper .box:nth-child(2) {
animation: translateAnimateY110 3000ms infinite 1000ms;
background-color: #03a9f4;
}
.translate-animation-demo-wrapper .box:nth-child(2)::before {
content: "translateY(110px)";
}
.translate-animation-demo-wrapper .box:nth-child(3) {
animation: translateAnimateX50Y100 3000ms infinite 1000ms;
background-color: palevioletred;
}
.translate-animation-demo-wrapper .box:nth-child(3)::before {
content: "translate(-50px, -100px)";
}
@keyframes translateAnimateX50 {
0% {
transform: translateX(0);
}
50% {
transform: translateX(50px);
}
100% {
transform: translateX(0);
}
}
@keyframes translateAnimateY110 {
0% {
transform: translateY(0);
}
50% {
transform: translateY(110px);
}
100% {
transform: translateY(0);
}
}
@keyframes translateAnimateX50Y100 {
0% {
transform: translate(0, 0);
}
50% {
transform: translate(-50px, -100px);
}
100% {
transform: translate(0, 0);
}
}
@media (max-width: 700px) {
.translate-animation-demo-wrapper .container {
gap: 20px;
min-height: auto;
}
.translate-animation-demo-wrapper .box {
width: 160px;
height: 110px;
margin: 0;
}
.translate-animation-demo-wrapper .box::before {
font-size: 14px;
text-align: center;
padding: 0 8px;
}
}
</style>
<!-- JavaScript -->
<script>
/* No JavaScript needed */
</script>
Rotate
Rotation turns an element around its transform origin.
transform: rotate(45deg);
transform: rotate(-45deg);
transform: rotate(0.5turn);
- positive values rotate clockwise
- negative values rotate the opposite way
0.5turnis the same as180deg
center point
+
/ \
/ \ rotate around this point
Rotate Example
This is an extra example added to make rotation easier to see.
Code:
<div class="rotate-demo-wrapper">
<div class="rotate-demo-card">
<div class="rotate-demo-arrow"></div>
</div>
</div>
<style>
.rotate-demo-wrapper {
padding: 20px 0 10px;
}
.rotate-demo-wrapper .rotate-demo-card {
width: min(100%, 280px);
height: 180px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 18px;
background: linear-gradient(135deg, #fff1d6, #d7f1ff);
border: 1px solid rgba(0, 0, 0, 0.08);
}
.rotate-demo-wrapper .rotate-demo-arrow {
width: 84px;
height: 84px;
position: relative;
animation: rotateDemoArrow 2800ms infinite ease-in-out;
}
.rotate-demo-wrapper .rotate-demo-arrow::before,
.rotate-demo-wrapper .rotate-demo-arrow::after {
content: "";
position: absolute;
background-color: #1d4f91;
border-radius: 999px;
}
.rotate-demo-wrapper .rotate-demo-arrow::before {
width: 12px;
height: 84px;
left: 36px;
top: 0;
}
.rotate-demo-wrapper .rotate-demo-arrow::after {
width: 48px;
height: 12px;
top: 0;
left: 18px;
transform: rotate(45deg);
transform-origin: left center;
}
@keyframes rotateDemoArrow {
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(45deg);
}
100% {
transform: rotate(0deg);
}
}
</style>
Sliding Overlay Pattern
A very common transform pattern is the hidden overlay effect.
1. place overlay over the parent
2. move overlay outside with translate()
3. hide overflow on the parent
4. bring overlay back on hover
5. animate transform with transition
This pattern is used in cards, galleries, product blocks, and image captions.
Overlay Example 1: Sliding Panel
Code:
<!-- HTML -->
<div class="overlay-demo-wrapper">
<h1>Move the mouse over the square</h1>
<div class="box">
<div class="overlay">
<p>
This content is hidden through transformation and only appears when
hovering over <code>div.box</code>
</p>
</div>
</div>
</div>
<!-- CSS -->
<style>
.overlay-demo-wrapper,
.overlay-demo-wrapper * {
box-sizing: border-box;
}
.overlay-demo-wrapper {
line-height: 1.5;
font-family: sans-serif;
background-color: #f9f9fd;
padding: 20px;
}
.overlay-demo-wrapper h1 {
text-align: center;
font-size: 24px;
margin-bottom: 20px;
}
.overlay-demo-wrapper .box {
position: relative;
width: 300px;
height: 300px;
margin: 0 auto;
background-color: #bdbdbd;
overflow: hidden;
}
.overlay-demo-wrapper .overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #3f51b5;
transform: translateY(100%);
transition: transform 250ms ease-in-out;
}
.overlay-demo-wrapper .box:hover .overlay,
.overlay-demo-wrapper .box.active .overlay {
transform: translateY(0);
}
.overlay-demo-wrapper .overlay p {
color: #fff;
padding: 10px;
margin: 0;
font-size: 18px;
}
.overlay-demo-wrapper .overlay code {
display: inline-block;
padding: 2px 4px;
border-radius: 2px;
background-color: #fff;
color: #2a2a2a;
}
@media (max-width: 480px) {
.overlay-demo-wrapper .box {
width: 100%;
max-width: 300px;
height: 300px;
}
.overlay-demo-wrapper .overlay p {
font-size: 16px;
}
}
</style>
<!-- JavaScript -->
<script>
const overlayBox = document.querySelector(".overlay-demo-wrapper .box");
if (overlayBox) {
overlayBox.addEventListener("click", function () {
overlayBox.classList.toggle("active");
});
}
</script>
Overlay Example 2: Gallery Cards
This gallery uses the same idea, but the overlay moves upward with translateY(100%).
Code:
<!-- HTML -->
<div class="gallery-overlay-demo-wrapper">
<ul class="gallery">
<li class="gallery-item">
<img
src="https://cdn.pixabay.com/photo/2020/11/26/11/48/cat-5778777_1280.jpg"
alt="cat"
/>
<div class="overlay">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident
temporibus neque, ad debitis magni?
</div>
</li>
<li class="gallery-item">
<img
src="https://cdn.pixabay.com/photo/2023/03/27/14/18/british-shorthair-7880879_1280.jpg"
alt="cat"
/>
<div class="overlay">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident
temporibus neque, ad debitis magni?
</div>
</li>
<li class="gallery-item">
<img
src="https://cdn.pixabay.com/photo/2023/04/07/07/14/cat-7905702_1280.jpg"
alt="cat"
/>
<div class="overlay">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquam provident
temporibus neque, ad debitis magni?
</div>
</li>
</ul>
</div>
<!-- CSS -->
<style>
.gallery-overlay-demo-wrapper,
.gallery-overlay-demo-wrapper * {
box-sizing: border-box;
}
.gallery-overlay-demo-wrapper .gallery {
margin: 0;
padding: 0;
list-style: none;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.gallery-overlay-demo-wrapper img {
display: block;
max-width: 100%;
width: 100%;
height: auto;
}
.gallery-overlay-demo-wrapper .gallery-item {
position: relative;
border: 1px solid #212121;
border-radius: 4px;
overflow: hidden;
flex: 1 1 300px;
}
.gallery-overlay-demo-wrapper .overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 8px;
background-color: #3f51b5;
color: #fff;
font-family: sans-serif;
line-height: 1.5;
transform: translateY(100%);
transition: transform 250ms ease-in-out;
}
.gallery-overlay-demo-wrapper .gallery-item:hover .overlay {
transform: translateY(0);
}
</style>
<!-- JavaScript -->
<script>
/* No JavaScript needed */
</script>
Centering With translate(-50%, -50%)
This is one of the most useful translate techniques.
<div class="parent">
<div class="box"></div>
</div>
.parent {
position: relative;
}
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
- Make the parent the positioning context with
position: relative. - Move the child’s top-left corner to the middle with
top: 50%andleft: 50%. - Pull the child back by half of its own size with
translate(-50%, -50%).
without translate:
parent center = +
box corner = +
with translate(-50%, -50%):
[ box ]
+
box center sits on parent center
Centering Example 1: Simple Center Demo
Code:
<div class="translate-centering-demo-wrapper">
<div class="parent">
<div class="box"></div>
</div>
</div>
<style>
.translate-centering-demo-wrapper {
padding: 20px 0;
}
.translate-centering-demo-wrapper .parent {
position: relative;
width: min(100%, 420px);
height: 240px;
margin: 0 auto;
border: 2px dashed #7a8b99;
border-radius: 16px;
background:
linear-gradient(135deg, rgba(3, 169, 244, 0.08), rgba(255, 165, 0, 0.14));
}
.translate-centering-demo-wrapper .box {
position: absolute;
top: 50%;
left: 50%;
width: 120px;
height: 120px;
border-radius: 14px;
background-color: #03a9f4;
box-shadow:
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);
transform: translate(-50%, -50%);
}
.translate-centering-demo-wrapper .box::before {
content: "center";
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
font-weight: 600;
color: #fff;
}
@media (max-width: 600px) {
.translate-centering-demo-wrapper .parent {
height: 200px;
}
.translate-centering-demo-wrapper .box {
width: 100px;
height: 100px;
}
}
</style>
Centering Example 2: Framed Box
Code:
<!-- HTML -->
<div class="centered-box-demo-wrapper">
<div class="parent">
<div class="box"></div>
</div>
</div>
<!-- CSS -->
<style>
.centered-box-demo-wrapper .parent {
position: relative;
max-width: 500px;
height: 300px;
border: 10px solid #303f9f;
border-radius: 10px;
background-color: #3f51b5;
margin: 20px auto;
box-sizing: border-box;
}
.centered-box-demo-wrapper .box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100px;
height: 100px;
border: 10px solid #ffa000;
border-radius: 10px;
background-color: #ffc107;
box-sizing: border-box;
}
</style>
<!-- JavaScript -->
<script>
/* No JavaScript needed */
</script>
Skew
Skewing tilts the sides of an element.
transform: skewX(30deg);
transform: skewY(30deg);
transform: skew(30deg, 30deg);
If skew() gets only one value, the second value becomes 0, so it behaves like skewX().
normal box: [ ]
skewed box: /______/
tilted sides
Skew Example
Code:
<!-- HTML -->
<div class="skew-animation-demo-wrapper">
<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
</div>
<!-- CSS -->
<style>
.skew-animation-demo-wrapper .container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 12px;
padding: 20px;
box-sizing: border-box;
}
.skew-animation-demo-wrapper .box {
width: 180px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
margin-left: 5px;
margin-right: 5px;
box-shadow:
0px 2px 1px -1px rgba(0, 0, 0, 0.2),
0px 1px 1px 0px rgba(0, 0, 0, 0.14),
0px 1px 3px 0px rgba(0, 0, 0, 0.12);
box-sizing: border-box;
}
.skew-animation-demo-wrapper .box::before {
display: inline-block;
font-size: 16px;
}
.skew-animation-demo-wrapper .box:nth-child(1) {
animation: skewAnimateX30 3000ms infinite 1000ms;
background-color: orange;
}
.skew-animation-demo-wrapper .box:nth-child(1)::before {
content: "skewX(30deg)";
}
.skew-animation-demo-wrapper .box:nth-child(2) {
animation: skewAnimateY30 3000ms infinite 1000ms;
background-color: #03a9f4;
}
.skew-animation-demo-wrapper .box:nth-child(2)::before {
content: "skewY(30deg)";
}
.skew-animation-demo-wrapper .box:nth-child(3) {
animation: skewAnimateX30Y30 3000ms infinite 1000ms;
background-color: palevioletred;
}
.skew-animation-demo-wrapper .box:nth-child(3)::before {
content: "skew(30deg, 30deg)";
}
@keyframes skewAnimateX30 {
0% {
transform: skewX(0deg);
}
50% {
transform: skewX(30deg);
}
100% {
transform: skewX(0deg);
}
}
@keyframes skewAnimateY30 {
0% {
transform: skewY(0deg);
}
50% {
transform: skewY(30deg);
}
100% {
transform: skewY(0deg);
}
}
@keyframes skewAnimateX30Y30 {
0% {
transform: skew(0deg, 0deg);
}
50% {
transform: skew(30deg, 30deg);
}
100% {
transform: skew(0deg, 0deg);
}
}
@media (max-width: 700px) {
.skew-animation-demo-wrapper .container {
min-height: auto;
}
.skew-animation-demo-wrapper .box {
width: 160px;
height: 90px;
margin: 0;
}
.skew-animation-demo-wrapper .box::before {
font-size: 14px;
text-align: center;
padding: 0 8px;
}
}
</style>
<!-- JavaScript -->
<script>
/* No JavaScript needed */
</script>
Final Cheat Sheet
transform: scale(1.2); -> make bigger
transform: translateX(20px); -> move right
transform: translateY(20px); -> move down
transform: translate(-50%, -50%)-> center with absolute positioning
transform: rotate(45deg); -> turn
transform: skew(20deg, 10deg); -> tilt
Good animation friends:
transform
opacity
What To Remember
- Transforms work in 2D space and change the visual result of an element.
- The most common functions are
scale(),translate(),rotate(), andskew(). - Transforms usually do not affect the layout of nearby elements in the normal flow.
transformis one of the best properties to animate for smooth UI effects.- A very practical translate trick is
translate(-50%, -50%)for exact centering. - Overlay effects usually combine
position,overflow: hidden,translate(), andtransition.