—CSS Series · Post 11: Transitions, Transforms & CSS Variables
Transitions, Transforms
& CSS Variables
This post brings your sites to life. Smooth hover effects, subtle movement, and a reusable color system you can change in one place — these three tools are what separate static pages from polished products.
💬 "Why Does This Feel Cheap?"
The layout is correct. The colors are right. But when you hover over a button it snaps — no fade, no feel. The card jumps up without any softness. Everything technically works but nothing feels considered.
That snap is the absence of transitions. Transitions convert instant state changes into smooth animations, and they require a single line of CSS. This post covers the three properties that close the gap between "it works" and "it feels good."
🧱 Transitions
A transition tells CSS to animate from the old value to the new value when a property changes (on hover, on focus, on class change). Without a transition, the change is instant. With a transition, it's smooth.
/* transition: property duration timing-function */
.button {
background: rgba(255,105,180,0.15);
color: #ff69b4;
transition: all 0.3s ease; /* smooth ALL changing properties */
}
.button:hover {
background: #ff69b4;
color: #fff;
}
/* Transition specific properties — better for performance */
.card {
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
/* Timing functions */
/* ease — slow start, fast middle, slow end (default) */
/* ease-in — slow start, fast end */
/* ease-out — fast start, slow end (feels most natural) */
/* linear — same speed throughout */
/* ease-in-out — slow start and end */transition: all is convenient but animates everything — including properties like display and height that don't animate well. For production code, list specific properties: transition: transform 0.3s ease, opacity 0.3s ease. It's more intentional and performs better.
🧱 Transform — Move, Scale, Rotate
The transform property manipulates an element's position, size, and rotation without affecting layout. Other elements don't shift to accommodate it. It happens at the compositing layer — which makes it fast and smooth.
Card A
Lifts on hover
Card B
Smooth lift
/* translateX / translateY — move horizontally / vertically */
.card:hover {
transform: translateY(-6px); /* lift up 6px */
}
/* scale — grow or shrink */
.icon:hover {
transform: scale(1.1); /* 10% bigger */
}
/* rotate */
.arrow:hover {
transform: rotate(90deg);
}
/* Combine multiple transforms */
.button:hover {
transform: translateY(-2px) scale(1.02);
}
/* Always add transition on the BASE state — not the :hover */
.button {
transition: transform 0.2s ease; /* ← here, not on :hover */
}If you put transition only on :hover, the effect plays when entering the hover state but snaps back instantly when the cursor leaves. Always put transition on the base element — then it animates both in and out smoothly.
opacity — Fade In and Out
/* Fade an overlay in on hover */
.card-overlay {
opacity: 0;
transition: opacity 0.3s ease;
}
.card:hover .card-overlay {
opacity: 1;
}
/* Dim inactive items */
.nav-item {
opacity: 0.6;
transition: opacity 0.2s ease;
}
.nav-item:hover,
.nav-item.active {
opacity: 1;
}🧱 CSS Variables — A Reusable Design System
CSS custom properties (variables) let you define a value once and reuse it everywhere. Change it in one place and every element that references it updates instantly. This is how real design systems work — one source of truth for colors, spacing, and typography.
/* Define variables on :root — available everywhere */
:root {
/* Colors */
--color-bg: #0a0a0f;
--color-surface: #12121a;
--color-text: #e0e0e8;
--color-muted: #9595a8;
--color-accent: #ff69b4;
/* Transparent layers built from the accent */
--accent-glow: rgba(255, 105, 180, 0.15);
--accent-border: rgba(255, 105, 180, 0.25);
/* Spacing */
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 48px;
/* Typography */
--font-body: 'Space Grotesk', sans-serif;
--font-mono: 'Fira Code', monospace;
--radius-card: 12px;
}
/* Use them with var() */
body {
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-body);
}
.card {
background: var(--color-surface);
border: 1px solid var(--accent-border);
border-radius: var(--radius-card);
padding: var(--space-lg);
transition: border-color 0.3s ease;
}
.card:hover {
border-color: var(--color-accent);
}Want to change your accent from pink to purple? Change --color-accent on :root once. Every border, every hover state, every text that uses var(--color-accent) updates instantly — across the whole site. This is the professional reason to use variables, not just convenience.
Mini Build: Card with Hover Effects + Variable Theme
Build a card that lifts on hover with a smooth transition, using CSS variables for all colors. Then swap the entire theme by changing two variable values.
Define your design token variables on :root.
:root {
--bg: #0a0a0f;
--surface: #12121a;
--accent: #ff69b4; /* change this to rebrand */
--glow: rgba(255,105,180,0.12);
--text: #e0e0e8;
--muted: #9595a8;
--radius: 12px;
--transition: 0.3s ease;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: var(--bg); color: var(--text); font-family: sans-serif; padding: 40px 24px; }Build the card with transitions tied to variables.
.card {
background: var(--surface);
border: 1px solid rgba(255,255,255,0.07);
border-radius: var(--radius);
padding: 24px;
max-width: 320px;
transition: transform var(--transition),
box-shadow var(--transition),
border-color var(--transition);
}
.card:hover {
transform: translateY(-6px);
box-shadow: 0 20px 48px rgba(0,0,0,0.5);
border-color: var(--accent);
background: var(--glow);
}
.card-tag {
font-size: 0.75rem;
color: var(--accent);
text-transform: uppercase;
letter-spacing: 1.5px;
margin-bottom: 10px;
display: block;
}Swap the theme in two lines. Change --accent to #69d2e7 and --glow to rgba(105,210,231,0.12). Your entire card — tag color, hover border, hover background — instantly switches. No hunting through 20 selectors.
💛 Your Sites Have a Pulse Now
The difference between a static page and a product that feels designed often comes down to two things: motion and consistency. Transitions add the motion. CSS variables enforce the consistency. Together they're the finish layer on everything you've built.
- transition smooths state changes — put it on the base element, not :hover
- transform: translateY() lifts elements. scale() grows them. rotate() turns them.
- Transform doesn't affect layout — other elements don't move
- opacity fades. Pair with transition for smooth reveal effects.
- CSS variables (--name: value) defined on :root are available everywhere
- Use var(--name) to reference. Change once, updates everywhere.
Next: Shadows, Depth & Advanced Selectors — box-shadow, ::before/::after, and attribute selectors. The finishing details that make a site feel properly crafted.
For an embarrassingly long time I had transition on my :hover state. Not on the base. On the hover. Which meant hovering in was smooth but leaving was an instant snap, and I genuinely thought that was just how CSS transitions worked.
Someone finally pointed it out in a code review. "Why is your transition only on :hover?" I looked at it. I thought about it. I moved it to the base. Both directions became smooth. That code review note quietly improved every interactive element I've written since.
Base element. Not :hover. Every time.