CSS Tutorial / CSS Transforms and Transitions
Transforms move, rotate, or scale a box's rendered appearance without reflowing surrounding content. Transitions animate a change between property values, so select the properties and timing deliberately and provide a reduced-motion alternative.
Understand transforms and normal flow
Document flow refers to the relationship between where an element is originally placed and surrounding elements. Even if you move the button up to translateY(-.5rem), the paragraph below does not move up according to the space where the button was moved. Transformed elements may overlap surrounding content or go off-screen, so leaving enough room in their original placement is a priority rather than increasing the movement distance.
| function | yes | Results to Observe |
|---|---|---|
translate() |
translate(1rem, -.5rem) |
Move 1rem to the right, .5rem up |
rotate() |
rotate(15deg) |
Rotate around a reference point |
scale() |
scale(1.05) |
Displayed at 1.05 times the original size |
Just because the size appears larger does not mean that the space occupied by the box before conversion will be larger. Review the Grid or Width properties to change the actual column width of the card, or Transform to create a slightly larger visual effect when pressed. Inline boxes that are not replaced, such as plain text span , are not converted, so specify display: inline-block if necessary. Definitions of transformation targets and coordinate processing can be found in the MDN transform document.
Compare transform order and origin
When you list transformation functions, each function affects the coordinate system used by the next function. Therefore, translation/rotation and rotation/translation are generally not the same result. In the example below, A's rotated box is visible where it has been moved to the right, and B's box has moved in the direction affected by the rotation and is visible further down. Don't just look at the function name and assume that the two results are the same; compare their actual locations.
The center of rotation and expansion is set to transform-origin. If you use left top instead of the default center, it will rotate about the upper left corner. This is useful when you want to fix a specific point, such as when a menu opens, but the overlap with surrounding content can change just by changing the reference point.
Save the following code as transform-compare.html in UTF-8 and open it in your browser. No external files or libraries are required.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Transform order and origin</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.comparisons { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); gap: 1rem; }
.slot { padding: 1rem; border: 1px solid #8a9ba7; }
.frame { height: 9rem; display: grid; place-items: center; background: #f0f2f4; }
.box { width: 4rem; height: 2rem; display: grid; place-items: center; background: #166b92; color: white; }
.first { transform: translateX(2rem) rotate(45deg); }
.second { transform: rotate(45deg) translateX(2rem); }
.center { transform: rotate(30deg); transform-origin: center; }
.corner { transform: rotate(30deg); transform-origin: left top; }
</style>
</head>
<body>
<main>
<h1>The same value also varies depending on the order and reference point</h1>
<div class="comparisons">
<section class="slot"><h2>Translate, then rotate</h2><div class="frame"><div class="box first">A</div></div></section>
<section class="slot"><h2>Rotate, then translate</h2><div class="frame"><div class="box second">B</div></div></section>
<section class="slot"><h2>Center origin</h2><div class="frame"><div class="box center">C</div></div></section>
<section class="slot"><h2>Top-left origin</h2><div class="frame"><div class="box corner">D</div></div></section>
</div>
</main>
</body>
</html>
- Compare the positions of A and B. If you change the function order of
.secondto be the same as A, the relative positions of the two boxes will be the same. After confirmation, return it to the original order. - C and D have the same rotation angle. Change only
transform-originfrom.cornertocenter, check the difference in reference points, and restore toleft top. - Compare the magnification by changing only the rotation of C to
scale(1.2). The area outside the box will grow, but the surrounding area will not be relocated. After confirmation, restore it torotate(30deg).
Choose transition properties and timing
transition does not change state by itself. The change starts only when the CSS value changes on hover, focus, or class change. Specifies the property name to effect, duration, progression curve, and startup delay; if delay is omitted, it defaults to 0 seconds.
transition: transform 180ms ease 0ms, background-color 180ms ease 0ms;
Here, 180ms is 0.18 seconds, and ease is a curve that smoothly connects the beginning and end rather than proceeding only at the same speed. linear changes at a constant rate. Use short times and no delays first, then adjust as needed. Adding a long delay to a seemingly unresponsive button can make it difficult to notice the results of your actions.
Putting transition on the base rule allows it to operate on both entry and return. A transition declared only on :hover disappears when hover ends and can produce a different return. Name the properties deliberately instead of using all. This example transitions transform and background-color; not every property interpolates in the same way. For further detail, see MDN transition document.
Test hover and keyboard focus
Save the code below as transition-state.html. When you hover over the button or use the Tab key, the button moves up and its background color changes. Focus points are individually outlined in dark blue. The button is for practice purposes only to observe changes in state, and no additional action is performed after pressing the button.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Transforms and state transitions exercise</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; padding: 1rem; color: #183044; font-family: sans-serif; line-height: 1.6; }
main { max-width: 46rem; margin-inline: auto; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.2rem; }
button, input { font: inherit; }
button { cursor: pointer; }
:focus-visible { outline: 3px solid #164f73; outline-offset: 3px; }
.stage { padding: 2rem 1rem; border: 2px dashed #8a9ba7; }
.action { padding: .75rem 1.25rem; border: 1px solid #166b92; border-radius: .4rem;
color: #183044; background: #eaf4fa; transform: translateY(0);
transition: transform 180ms ease, background-color 180ms ease; }
.action:hover, .action:focus-visible { transform: translateY(-.5rem); background: #cfe8f4; }
.neighbor { margin-block: 1rem 0; padding: .75rem; background: #f0f2f4; }
@media (prefers-reduced-motion: reduce) {
.action { transition: none; }
.action:hover, .action:focus-visible { transform: none; }
}
</style>
</head>
<body>
<main>
<h1>The paragraph stays in place as the button moves</h1>
<p>Hover over the button or press Tab to focus it.</p>
<div class="stage">
<button class="action" type="button">Check state</button>
<p class="neighbor">This paragraph stays in place when the button is transformed.</p>
</div>
</main>
</body>
</html>
- Remember the top position of the paragraph below and hover over the button. Only the buttons move and the paragraphs must remain in the same place.
- Change the duration in two places from
180msto800msto observe entry and return, then return to the original value. - Leave the time as is and change only the progress curve to
linearfor comparison. Then restore it toease. - We only add a
400msdelay to the end of the first transition. The background color and movement start point change. Eliminates delays after confirmation. - Focus on the button with the Tab key and exit with Shift+Tab. Make sure the focus outline and background color describe the operating state.
Reduce motion and check side effects
If the user has set it to reduce movement, the prefers-reduced-motion: reduce condition will eliminate the movement, leaving behind background color and focus marks. If you just remove the transition, the change will occur immediately, but the final position you moved to will remain. In the example above, we decided that decorative movement itself was unnecessary, so even the hover and focus transforms were changed to none. Please refer to MDN motion reduction setting guide.
Compare the two states by changing your operating system settings or reproducing the media characteristics in the Rendering panel of your browser developer tools. In the reduced setting, the button should not move, only the color and focus indicator should change. When you're done with the lab, restore the emulation to its default state.
A non-none transform creates a stacking context and can establish the containing block for absolute and fixed descendants. If a popup or fixed element behaves unexpectedly, inspect transformed ancestors. Also check overlap, keyboard focus, and reduced motion; a smooth transition alone does not establish usable layout. See CSS position: relative, absolute, fixed, and sticky, CSS z-index and Stacking Contexts.










