CSS Transitions
CSS transitions allow property changes to occur smoothly over a specified duration, rather than instantaneously. When an element's property value changes — whether triggered by a class toggle, :hover pseudo-class, or JavaScript DOM manipulation — the transition interpolates between the old and new values.
Transitions are the simplest form of CSS animation: they define a starting state, an ending state, and how the interpolation between them should behave. For multi-step sequences, use CSS Animations with @keyframes instead.
| 1 | .button { |
| 2 | background: #1A1A2E; |
| 3 | color: #A0A0A0; |
| 4 | transition: background 0.3s ease, color 0.3s ease; |
| 5 | } |
| 6 | |
| 7 | .button:hover { |
| 8 | background: #00FF41; |
| 9 | color: #0D0D0D; |
| 10 | } |
The transition-property specifies which CSS properties should be transitioned when their values change. You can list multiple properties separated by commas, or use the all keyword to transition every animatable property.
| 1 | .element { |
| 2 | /* Transition specific properties */ |
| 3 | transition-property: transform, opacity, background; |
| 4 | |
| 5 | /* Transition everything (less performant) */ |
| 6 | transition-property: all; |
| 7 | |
| 8 | /* Transition nothing */ |
| 9 | transition-property: none; |
| 10 | } |
| 11 | |
| 12 | /* Best practice: be explicit about what to transition */ |
| 13 | .explicit { |
| 14 | transition-property: transform, opacity; |
| 15 | /* Only these two properties will animate */ |
| 16 | } |
warning
transition-duration defines how long the transition takes to complete. Values are specified in seconds (s) or milliseconds (ms). When multiple properties are being transitioned, you can specify different durations for each.
| 1 | .element { |
| 2 | /* Single duration for all properties */ |
| 3 | transition-duration: 0.3s; |
| 4 | |
| 5 | /* Multiple durations — one per property */ |
| 6 | transition-property: transform, opacity; |
| 7 | transition-duration: 0.3s, 0.2s; |
| 8 | /* transform: 0.3s, opacity: 0.2s */ |
| 9 | } |
| 10 | |
| 11 | /* Typical durations for different effects */ |
| 12 | .micro-interaction { transition-duration: 150ms; } |
| 13 | .button { transition-duration: 200ms; } |
| 14 | .card-hover { transition-duration: 300ms; } |
| 15 | .page-enter { transition-duration: 500ms; } |
| 16 | } |
The timing function (easing curve) controls the rate of change during the transition. It determines whether the animation starts fast and slows down, starts slow and speeds up, or moves at a constant pace.
| 1 | .element { |
| 2 | /* Keyword timing functions */ |
| 3 | transition-timing-function: ease; /* slow start, fast middle, slow end (default) */ |
| 4 | transition-timing-function: linear; /* constant speed */ |
| 5 | transition-timing-function: ease-in; /* slow start, fast end */ |
| 6 | transition-timing-function: ease-out; /* fast start, slow end */ |
| 7 | transition-timing-function: ease-in-out; /* slow start and end */ |
| 8 | |
| 9 | /* Custom cubic-bezier curve */ |
| 10 | transition-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); |
| 11 | |
| 12 | /* Step functions — discrete jumps */ |
| 13 | transition-timing-function: steps(4, end); |
| 14 | transition-timing-function: step-start; |
| 15 | transition-timing-function: step-end; |
| 16 | } |
| Function | cubic-bezier | Character | Best For |
|---|---|---|---|
| ease | (0.25, 0.1, 0.25, 1) | Gentle start and end | General purpose |
| linear | (0, 0, 1, 1) | Constant speed | Progress bars, loaders |
| ease-in | (0.42, 0, 1, 1) | Slow → fast | Objects leaving screen |
| ease-out | (0, 0, 0.58, 1) | Fast → slow | Objects entering screen |
| ease-in-out | (0.42, 0, 0.58, 1) | Slow both ends | Reversible transitions |
| 1 | .spring { |
| 2 | /* Custom spring-like curve — overshoots then settles */ |
| 3 | transition: transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); |
| 4 | } |
| 5 | |
| 6 | .smooth-out { |
| 7 | /* Smooth deceleration — "ease out quint" */ |
| 8 | transition: opacity 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); |
| 9 | } |
| 10 | |
| 11 | .snappy { |
| 12 | /* Quick responsive feel for micro-interactions */ |
| 13 | transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); |
| 14 | } |
transition-delay specifies how long to wait before the transition begins. A positive value delays the start; a negative value starts the transition mid-way through its duration (as if it had already been running).
| 1 | .element { |
| 2 | /* Start after 0.2s delay */ |
| 3 | transition-delay: 0.2s; |
| 4 | |
| 5 | /* Negative delay — starts mid-transition */ |
| 6 | transition-delay: -0.5s; |
| 7 | /* If duration is 1s, starts at 50% progress */ |
| 8 | } |
| 9 | |
| 10 | /* Staggered delays for lists */ |
| 11 | .item:nth-child(1) { transition-delay: 0s; } |
| 12 | .item:nth-child(2) { transition-delay: 0.1s; } |
| 13 | .item:nth-child(3) { transition-delay: 0.2s; } |
| 14 | .item:nth-child(4) { transition-delay: 0.3s; } |
| 15 | .item:nth-child(5) { transition-delay: 0.4s; } |
The transition shorthand combines all four transition sub-properties into a single declaration. The order is: property | duration | timing-function | delay.
| 1 | .element { |
| 2 | /* Shorthand: property duration timing-function delay */ |
| 3 | transition: transform 0.3s ease 0s; |
| 4 | |
| 5 | /* Multiple transitions — comma-separated */ |
| 6 | transition: |
| 7 | transform 0.3s ease, |
| 8 | opacity 0.2s ease 0.1s, |
| 9 | background 0.3s ease; |
| 10 | |
| 11 | /* Single value — sets duration only */ |
| 12 | transition: 0.3s; |
| 13 | |
| 14 | /* Global values */ |
| 15 | transition: inherit; |
| 16 | transition: initial; |
| 17 | transition: unset; |
| 18 | } |
| 19 | |
| 20 | /* Common patterns */ |
| 21 | .fade-in { |
| 22 | transition: opacity 0.25s ease; |
| 23 | } |
| 24 | |
| 25 | .slide-up { |
| 26 | transition: transform 0.3s cubic-bezier(0.215, 0.61, 0.355, 1); |
| 27 | } |
| 28 | |
| 29 | .interactive { |
| 30 | transition: |
| 31 | transform 0.2s ease, |
| 32 | box-shadow 0.2s ease, |
| 33 | background 0.15s ease; |
| 34 | } |
Not all CSS properties can be transitioned. Animatable properties are those that accept numeric values, colors, or discrete enumerated values. Below are the key categories of animatable properties.
| Category | Properties | Performance |
|---|---|---|
| Transforms | transform (translate, rotate, scale, skew) | Best (compositor) |
| Opacity | opacity | Best (compositor) |
| Filters | filter (blur, brightness, contrast, etc.) | Good (GPU) |
| Colors | color, background-color, border-color | Good (paint) |
| Dimensions | width, height, padding, margin, border-width | Poor (layout) |
| Positioning | top, right, bottom, left | Poor (layout) |
| 1 | .good { |
| 2 | /* Compositor-only — 60fps safe */ |
| 3 | transition: transform 0.3s ease, opacity 0.3s ease; |
| 4 | } |
| 5 | |
| 6 | .ok { |
| 7 | /* Triggers paint — still smooth but heavier */ |
| 8 | transition: color 0.2s ease, background-color 0.2s ease; |
| 9 | } |
| 10 | |
| 11 | .bad { |
| 12 | /* Triggers layout — avoid for animation */ |
| 13 | transition: width 0.3s ease, height 0.3s ease; |
| 14 | } |
| 15 | |
| 16 | /* Instead of animating width/height, use transforms */ |
| 17 | .better { |
| 18 | transition: transform 0.3s ease; |
| 19 | /* transform: scaleX(1.5) instead of width: 150% */ |
| 20 | } |
Transitions are triggered whenever a property value changes. The most common triggers are pseudo-classes like :hover and :focus, but any value change — including JavaScript-driven class toggles and inline style manipulation — will trigger a transition.
| 1 | .trigger-hover { |
| 2 | transition: transform 0.3s ease; |
| 3 | } |
| 4 | |
| 5 | .trigger-hover:hover { |
| 6 | transform: scale(1.1); |
| 7 | } |
| 8 | |
| 9 | .trigger-focus { |
| 10 | transition: box-shadow 0.2s ease; |
| 11 | } |
| 12 | |
| 13 | .trigger-focus:focus { |
| 14 | box-shadow: 0 0 0 3px rgba(0, 255, 65, 0.4); |
| 15 | outline: none; |
| 16 | } |
| 17 | |
| 18 | .trigger-class { |
| 19 | transition: opacity 0.3s ease, transform 0.3s ease; |
| 20 | } |
| 21 | |
| 22 | .trigger-class.active { |
| 23 | opacity: 1; |
| 24 | transform: translateY(0); |
| 25 | } |
| 26 | |
| 27 | .trigger-class:not(.active) { |
| 28 | opacity: 0; |
| 29 | transform: translateY(10px); |
| 30 | } |
Terminal-themed interfaces benefit from subtle, responsive hover effects. These micro-interactions make the interface feel alive and responsive without being distracting.
| 1 | .terminal-link { |
| 2 | color: #00FF41; |
| 3 | text-decoration: none; |
| 4 | position: relative; |
| 5 | transition: color 0.2s ease; |
| 6 | } |
| 7 | |
| 8 | .terminal-link::after { |
| 9 | content: ''; |
| 10 | position: absolute; |
| 11 | bottom: -2px; |
| 12 | left: 0; |
| 13 | width: 0; |
| 14 | height: 1px; |
| 15 | background: #00FF41; |
| 16 | transition: width 0.3s ease; |
| 17 | } |
| 18 | |
| 19 | .terminal-link:hover { |
| 20 | color: #00FF41; |
| 21 | } |
| 22 | |
| 23 | .terminal-link:hover::after { |
| 24 | width: 100%; |
| 25 | } |
| 26 | |
| 27 | /* Terminal button glow */ |
| 28 | .terminal-btn { |
| 29 | background: #1A1A2E; |
| 30 | border: 1px solid #222222; |
| 31 | color: #E0E0E0; |
| 32 | padding: 8px 16px; |
| 33 | border-radius: 4px; |
| 34 | font-family: monospace; |
| 35 | transition: |
| 36 | border-color 0.2s ease, |
| 37 | box-shadow 0.2s ease, |
| 38 | color 0.2s ease; |
| 39 | } |
| 40 | |
| 41 | .terminal-btn:hover { |
| 42 | border-color: #00FF41; |
| 43 | box-shadow: 0 0 12px rgba(0, 255, 65, 0.15); |
| 44 | color: #00FF41; |
| 45 | } |
| 46 | |
| 47 | /* Scanline accent pulse */ |
| 48 | .scanline-accent { |
| 49 | transition: opacity 0.3s ease; |
| 50 | } |
| 51 | |
| 52 | .scanline-accent:hover { |
| 53 | animation: scanPulse 0.5s ease; |
| 54 | } |
| 55 | |
| 56 | @keyframes scanPulse { |
| 57 | 0%, 100% { opacity: 1; } |
| 58 | 50% { opacity: 0.7; } |
| 59 | } |
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.