Modern CSS Transitions
Polished hover and focus transitions for buttons, cards and links that work on any .btn or .card.
1Liquid Ripple
A button that fills with a swirling liquid from the bottom on hover. Class: fx-liquid-ripple
<div class="d-flex flex-wrap align-items-center justify-content-center gap-3 p-3 h-100" style="min-height:200px">
<button type="button" class="btn btn-lg px-4 fx-liquid-ripple">Subscribe</button>
<button type="button" class="btn rounded-pill fx-liquid-ripple">Hover me</button>
</div>
/* CSS (already in assets/css/effects.css) */
.fx-liquid-ripple, .btn.fx-liquid-ripple {
position: relative; isolation: isolate; overflow: hidden; z-index: 0;
color: var(--wt-primary); background: var(--wt-surface, #fff); border: 2px solid var(--wt-primary);
transition: color .5s ease;
}
.fx-liquid-ripple::before {
content: ""; position: absolute; z-index: -1; left: 50%; top: 100%; width: 240%; aspect-ratio: 1;
margin-left: -120%; margin-top: 12%; border-radius: 42%;
background: var(--wt-primary);
transition: top .8s ease, margin-top .8s ease;
animation: fx-liquid-ripple-swirl 5s linear infinite;
}
.fx-liquid-ripple:hover, .fx-liquid-ripple:focus-visible, .btn.fx-liquid-ripple:hover, .btn.fx-liquid-ripple:focus-visible { color: var(--wt-on-primary, #fff); border-color: var(--wt-primary); }
.fx-liquid-ripple:hover::before, .fx-liquid-ripple:focus-visible::before { top: 50%; margin-top: -120%; }
.fx-liquid-ripple:active { transform: scale(.97); }
@keyframes fx-liquid-ripple-swirl { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
.fx-liquid-ripple, .fx-liquid-ripple::before, .fx-liquid-ripple::after, .fx-liquid-ripple *, .fx-liquid-ripple *::before, .fx-liquid-ripple *::after { animation: none !important; transition: none !important; }
}2Split Text Fill
Fills a heading or link with the primary colour from both sides at once on hover - top half from the left, bottom half from the right. Class: fx-split-text-fill
<div class="d-flex flex-wrap align-items-center justify-content-center gap-3 p-3 h-100" style="min-height:200px">
<a href="#" class="display-6 fw-bold fx-split-text-fill">Hover this title</a>
</div>
/* CSS (already in assets/css/effects.css) */
.fx-split-text-fill {
display: inline-block; text-decoration: none;
-webkit-text-fill-color: transparent;
background-image: linear-gradient(var(--wt-primary), var(--wt-primary)), linear-gradient(var(--wt-primary), var(--wt-primary)), linear-gradient(currentColor, currentColor);
background-repeat: no-repeat;
background-position: 0 0, 100% 100%, 0 0;
background-size: 0% 50%, 0% 50%, 100% 100%;
-webkit-background-clip: text; background-clip: text;
transition: background-size .6s cubic-bezier(.7, 0, .3, 1);
}
.fx-split-text-fill:hover, .fx-split-text-fill:focus-visible { background-size: 100% 50%, 100% 50%, 100% 100%; }
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
.fx-split-text-fill { -webkit-text-fill-color: currentColor; background: none; }
.fx-split-text-fill:hover { color: var(--wt-primary); }
}
@media (prefers-reduced-motion: reduce) {
.fx-split-text-fill, .fx-split-text-fill::before, .fx-split-text-fill::after, .fx-split-text-fill *, .fx-split-text-fill *::before, .fx-split-text-fill *::after { animation: none !important; transition: none !important; }
}3Elastic Scale
Buttons, chips and cards that grow with a rubbery overshoot on hover and squash when pressed. Class: fx-elastic-scale
<div class="d-flex flex-wrap align-items-center justify-content-center gap-3 p-3 h-100" style="min-height:200px">
<button type="button" class="btn btn-primary fx-elastic-scale">Add to cart</button>
<span class="badge rounded-pill text-bg-secondary fs-6 fx-elastic-scale">update</span> <a href="#" class="btn btn-outline-primary rounded-circle fs-5 fx-elastic-scale" aria-label="Cart, $32.15"><i class="bi bi-cart3" aria-hidden="true"></i></a>
</div>
/* CSS (already in assets/css/effects.css) */
.fx-elastic-scale { transition: transform .5s cubic-bezier(.34, 1.56, .64, 1); }
.fx-elastic-scale:hover, .fx-elastic-scale:focus-visible { transform: scale(1.08); }
.fx-elastic-scale:active { transform: scale(.94); transition-duration: .15s; }
@media (prefers-reduced-motion: reduce) {
.fx-elastic-scale, .fx-elastic-scale::before, .fx-elastic-scale::after, .fx-elastic-scale *, .fx-elastic-scale *::before, .fx-elastic-scale *::after { animation: none !important; transition: none !important; }
}