Have you noticed how the Tinkoff, Yandex or Alfa-Bank apps are just pleasant to use? Buttons respond to clicks, elements appear smoothly, downloads do not annoy, but on the contrary — they are pleasing to the eye. This is not magic or an army of designers. This is a set of specific techniques that you can master as well.

Why is this important at all?
The user will not say: "Wow, there is a transition with cubic-bezier!". But he will say: "What a nice application" or "It looks kind of cheap." High-quality animations work on a subconscious level:
They create a sense of speed - even if the server is slow
Give feedback — the user understands that their action has been taken into account
Reduce cognitive load — smooth transitions help the brain understand what is happening
They inspire confidence — details show that the team cares about the product
1. Skeletons instead of spinners
What's wrong with the spinners?
The spinning circle does not provide any information. The user does not know how long to wait and what is being loaded.
What they do in Tinkoff:
They show the "ghost" of future content — gray blocks in place of text, pictures, buttons. This is called a skeleton screen or skeleton.
.skeleton {
background: linear-gradient(
90deg,
#f0f0f0 25%,
#e0e0e0 50%,
#f0f0f0 75%
);
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: 4px;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}Why it works:
The brain perceives the skeleton as "the content is almost loaded", and not "you have to wait for an unknown amount of time". According to research, users estimate the loading time with skeletons to be 30-40% faster than with spinners.
2. Tactile response to actions
Button click effect:
When you press a button in the Yandex app, it "sinks" a little. This simulates a physical button.
.button {
background: #ffdd2d;
border: none;
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
}
.button:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}Ripple effect (wave):
When you click, a wave diverges from the point of click. This is a Material Design feature, but everyone uses it.
function createRipple(event) {
const button = event.currentTarget;
const circle = document.createElement('span');
const diameter = Math.max(button.clientWidth, button.clientHeight);
const radius = diameter / 2;
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - button.offsetLeft - radius}px`;
circle.style.top = `${event.clientY - button.offsetTop - radius}px`;
circle.classList.add('ripple');
const ripple = button.getElementsByClassName('ripple')[0];
if (ripple) {
ripple.remove();
}
button.appendChild(circle);
}3. Smart transitions between states
Bad:
Content just disappears and reappears. Quickly. Cheaply.
Good:
We use smooth transitions with transform and opacity.
.fade-enter {
opacity: 0;
transform: translateY(10px);
}
.fade-enter-active {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.fade-enter-to {
opacity: 1;
transform: translateY(0);
}Golden rule: Any change to the DOM must be animated. No sudden appearances/disappearances.
4. Micro-animation of forms
When focusing on input:
.input {
border: 2px solid #e0e0e0;
padding: 12px;
width: 100%;
transition: all 0.2s ease;
outline: none;
}
.input:focus {
border-color: #ffdd2d;
box-shadow: 0 0 0 4px rgba(255, 221, 45, 0.1);
}
.input-label {
position: absolute;
left: 12px;
top: 12px;
color: #999;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
pointer-events: none;
}
.input:focus + .input-label,
.input:not(:placeholder-shown) + .input-label {
top: -8px;
left: 8px;
font-size: 12px;
background: white;
padding: 0 4px;
color: #ffdd2d;
}Validation with animation:
.input-error {
border-color: #ff3333;
animation: shake 0.4s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
}5. Progress bars that don't drive you crazy
Bad progress bar: just moving. Boring.
Good progress bar: smooth, with a slight glow, shows specific stages.
.progress-bar {
width: 100%;
height: 6px;
background: #f0f0f0;
border-radius: 10px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #ffdd2d, #ffc700);
border-radius: 10px;
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
box-shadow: 0 0 10px rgba(255, 221, 45, 0.5);
}6. Modal windows with smart appearance
Incorrect: the window just pops up.
Correct: the background darkens, the window appears with a slight scaling and movement from bottom to top.
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0);
display: flex;
align-items: center;
justify-content: center;
transition: background 0.3s ease;
pointer-events: none;
}
.modal-overlay.active {
background: rgba(0, 0, 0, 0.5);
pointer-events: all;
}
.modal {
background: white;
border-radius: 16px;
padding: 24px;
max-width: 500px;
width: 90%;
transform: scale(0.9) translateY(20px);
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-overlay.active .modal {
transform: scale(1) translateY(0);
opacity: 1;
}
7. Cards with hover effects
Cards of goods, posts, projects — they should respond to hovering:
.card {
background: white;
border-radius: 12px;
padding: 20px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}
.card img {
transition: transform 0.3s ease;
}
.card:hover img {
transform: scale(1.05);
}8. Loading indicators for actions
When the user clicks "Send", the button should show that the process is in progress:
.submit-button {
position: relative;
min-width: 120px;
transition: all 0.3s ease;
}
.submit-button.loading {
pointer-events: none;
}
.spinner {
width: 20px;
height: 20px;
animation: rotate 1s linear infinite;
}
@keyframes rotate {
100% { transform: rotate(360deg); }
}9. Toasts and notifications
Principles:
Appear on the side (top right or left)
Smoothly enter
Automatically disappear after 3-5 seconds
Can be closed manually
.toast {
position: fixed;
top: 20px;
right: -300px;
background: white;
padding: 16px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 250px;
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.toast.show {
right: 20px;
}10. Important Animation Principles
Timing functions:
Don't just use ease or linear. Explore cubic-bezier:
ease-in-out - smoothly accelerates and decelerates
cubic-bezier(0.4, 0, 0.2, 1) — Material Design easing, very pleasant
cubic-bezier(0.25, 0.46, 0.45, 0.94) — easeOutQuad for fast animations
Duration:
Micro-interactions (hover, clicks): 100-200ms
Transitions between states: 200-300ms
Modal windows, drawers: 300-400ms
Page animations: 400-600ms
More than 600ms is already too slow.
60 FPS rule:
Animate only the properties that do not cause reflow:
✅
transform,opacity❌
width,height,top,left,margin
Conclusion
High-quality animations are not about complexity, but about details. You don't need to study 3D or physics. You just need to:
Add smoothness to everything
Give feedback on actions
Use the correct timing functions
Be consistent
Start with one button. Make it pleasant. Then another one. Then the forms. Then the cards. In a month, your app will look like a million.
All this and much more can be studied in Codice - in detail, with practical tasks and real examples. We analyze not only the theory, but also show how it all works in real projects.
And if you need support, have questions, or want to discuss the code, we have huge active community in Telegram channel! More than 2000 developers who will always help, advise and support.
