6 Pure CSS1 with JSPublished 2026-10-05Free · no sign-up
Seven hover effects for buttons: a fill that rises from the bottom, a label that slides away for an arrow, corner borders that grow into a frame, a hard offset shadow that presses flat, colour that closes in from both sides, a button that is pulled toward the pointer and a slanted fill.
An outlined button fills with colour from the bottom edge up on hover and focus, and the text colour flips to stay readable. The fill is a pseudo-element scaled on the y axis, kept behind the label with a negative z-index inside an isolated button.
On hover the label slides off to the right and an arrow slides in from the left to take its place. Both live in the button the whole time; the arrow is aria-hidden, so the accessible name is always the label text.
Two L-shaped borders sit in opposite corners and grow along the edges until they meet, forming a full frame. They are the button's ::before and ::after with only two borders each, and their width and height transition together.
A bold neo-brutalist button with a hard offset shadow. Hover pulls it further from its shadow and pressing pushes it flat into the shadow, so the button feels physical. The shadow is a box-shadow with no blur.
Two halves of colour slide in from both sides and meet in the middle on hover. Each half is a pseudo-element half the button's width, scaled from its own outer edge, which gives a curtain-closing feel.
The button leans toward the pointer as it approaches and springs back when it leaves, a playful effect for one hero action. Script moves it a fraction of the pointer's distance from its centre with transform, and does nothing when reduced motion is set.
var area = document.querySelector('.bh6'), b = area.querySelector('button');
if (!matchMedia('(prefers-reduced-motion: reduce)').matches) {
area.addEventListener('pointermove', function (e) { var r = b.getBoundingClientRect(), x = e.clientX - (r.left + r.width / 2), y = e.clientY - (r.top + r.height / 2); b.style.transform = 'translate(' + x * .25 + 'px,' + y * .35 + 'px)'; });
area.addEventListener('pointerleave', function () { b.style.transform = ''; });
}
07. Diagonal Skewed Fill
ButtonHover
A slanted block of colour slides across the button from the left, with its leading edge angled by skewX. The block starts outside the button, wider than it so the slant never leaves a gap, and overflow: hidden trims it to the rounded shape.
Put the fill in a pseudo-element with z-index: -1 and give the button isolation: isolate. The fill then sits above the button's background but below its text, without wrapping the label in another element.
Hover is a bonus
Touch screens do not hover, so the button must already look like a button at rest. Copy every hover state to :focus-visible and keep a clear outline for keyboard focus.
Check contrast in both states
When the fill changes the background, the text colour must change with it. Test the label against the starting background and the filled one.
Short and on transform
Hover transitions of 200 to 350ms feel responsive. Animate transform, opacity and colour; avoid animating padding, width or border width, which shift the layout.
Frequently asked questions
Can I combine these effects?
Yes, but keep one idea per button. Two competing motions on hover feel busy.
Is the magnetic effect accessible?
It only moves the button visually and is disabled for reduced motion; the button works normally with keyboard and touch.
Where are the buttons from the main buttons page?
The CSS Buttons collection has base styles; this page is about hover motion only.