Open demo

CSS code

HTML
<button type="button" class="sh sh-shine">Diagonal shine</button>
<button type="button" class="sh sh-wipe">Wipe fill</button>
<button type="button" class="sh sh-split">Split open</button>
<button type="button" class="sh sh-shift">Gradient shift</button>
CSS
/* Shine and sweep buttons: pseudo-elements inside an overflow:hidden button move on hover and on keyboard focus. */
.sh{position:relative;overflow:hidden;isolation:isolate;display:inline-block;border:0;border-radius:11px;padding:14px 30px;color:#fff;
  font:inherit;font-size:.92rem;font-weight:700;line-height:1.25;cursor:pointer}
.sh:focus-visible{outline:2px solid #a5b4fc;outline-offset:3px}
/* diagonal shine: a skewed white highlight crosses the button */
.sh-shine{background:linear-gradient(120deg,#4f46e5,#7c3aed)}
.sh-shine::after{content:"";position:absolute;top:0;left:-140%;width:60%;height:100%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.45),transparent);transform:skewX(-22deg);transition:left .55s ease}
.sh-shine:hover::after,.sh-shine:focus-visible::after{left:150%}
/* wipe fill: a darker layer slides in from the left behind the label */
.sh-wipe{background:#0277b8}
.sh-wipe::before{content:"";position:absolute;inset:0;z-index:-1;background:#075985;transform:translateX(-101%);transition:transform .3s ease}
.sh-wipe:hover::before,.sh-wipe:focus-visible::before{transform:none}
/* split open: two halves close in from the top and bottom */
.sh-split{background:#101828}
.sh-split::before,.sh-split::after{content:"";position:absolute;left:0;right:0;height:50%;z-index:-1;background:#e11d48;
  transition:transform .32s cubic-bezier(.4,0,.2,1)}
.sh-split::before{top:0;transform:translateY(-101%)}
.sh-split::after{bottom:0;transform:translateY(101%)}
.sh-split:hover::before,.sh-split:hover::after,.sh-split:focus-visible::before,.sh-split:focus-visible::after{transform:none}
/* gradient shift: a 220% wide gradient moves to its other end */
.sh-shift{background:linear-gradient(100deg,#166534,#15803d,#166534);background-size:220% 100%;transition:background-position .6s ease}
.sh-shift:hover,.sh-shift:focus-visible{background-position:100% 0}
@media (prefers-reduced-motion:reduce){.sh,.sh::before,.sh::after{transition:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<button type="button" class="btn btn-sweep btn-sweep-shine">Diagonal shine</button>
<button type="button" class="btn btn-sweep btn-sweep-wipe">Wipe fill</button>
<button type="button" class="btn btn-sweep btn-sweep-split">Split open</button>
<button type="button" class="btn btn-sweep btn-sweep-shift">Gradient shift</button>
CSS
/* .btn with its colours pinned through --bs-btn-* variables; the sweeps are pseudo-elements that move on hover and focus. */
.btn-sweep{--sw-bg:#101828;
  --bs-btn-padding-x:30px;--bs-btn-padding-y:14px;--bs-btn-font-size:.92rem;--bs-btn-font-weight:700;--bs-btn-line-height:1.25;
  --bs-btn-border-width:0;--bs-btn-border-radius:11px;--bs-btn-focus-box-shadow:none;
  --bs-btn-color:#fff;--bs-btn-hover-color:#fff;--bs-btn-active-color:#fff;
  --bs-btn-bg:var(--sw-bg);--bs-btn-hover-bg:var(--sw-bg);--bs-btn-active-bg:var(--sw-bg);
  position:relative;overflow:hidden;isolation:isolate}
.btn-sweep:focus-visible{outline:2px solid #a5b4fc;outline-offset:3px}
.btn-sweep-shine{--sw-bg:#4f46e5;background-image:linear-gradient(120deg,#4f46e5,#7c3aed)}
.btn-sweep-shine::after{content:"";position:absolute;top:0;left:-140%;width:60%;height:100%;
  background:linear-gradient(100deg,transparent,rgba(255,255,255,.45),transparent);transform:skewX(-22deg);transition:left .55s ease}
.btn-sweep-shine:hover::after,.btn-sweep-shine:focus-visible::after{left:150%}
.btn-sweep-wipe{--sw-bg:#0277b8}
.btn-sweep-wipe::before{content:"";position:absolute;inset:0;z-index:-1;background:#075985;transform:translateX(-101%);transition:transform .3s ease}
.btn-sweep-wipe:hover::before,.btn-sweep-wipe:focus-visible::before{transform:none}
.btn-sweep-split::before,.btn-sweep-split::after{content:"";position:absolute;left:0;right:0;height:50%;z-index:-1;background:#e11d48;
  transition:transform .32s cubic-bezier(.4,0,.2,1)}
.btn-sweep-split::before{top:0;transform:translateY(-101%)}
.btn-sweep-split::after{bottom:0;transform:translateY(101%)}
.btn-sweep-split:hover::before,.btn-sweep-split:hover::after,.btn-sweep-split:focus-visible::before,.btn-sweep-split:focus-visible::after{transform:none}
.btn-sweep-shift{--sw-bg:#166534;background-image:linear-gradient(100deg,#166534,#15803d,#166534);background-size:220% 100%;transition:background-position .6s ease}
.btn-sweep-shift:hover,.btn-sweep-shift:focus-visible{background-position:100% 0}
@media (prefers-reduced-motion:reduce){.btn-sweep,.btn-sweep::before,.btn-sweep::after{transition:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<button type="button" class="relative isolate cursor-pointer overflow-hidden rounded-[11px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-bold text-white focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] bg-[#4f46e5] bg-[linear-gradient(120deg,#4f46e5,#7c3aed)] after:absolute after:top-0 after:-left-[140%] after:h-full after:w-[60%] after:-skew-x-[22deg] after:bg-[linear-gradient(100deg,transparent,rgba(255,255,255,.45),transparent)] after:transition-[left] after:duration-[550ms] after:ease-[ease] hover:after:left-[150%] focus-visible:after:left-[150%] motion-reduce:after:transition-none">Diagonal shine</button>
<button type="button" class="relative isolate cursor-pointer overflow-hidden rounded-[11px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-bold text-white focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] bg-[#0277b8] before:absolute before:inset-0 before:-z-10 before:-translate-x-[101%] before:bg-[#075985] before:transition-transform before:duration-300 before:ease-[ease] hover:before:translate-x-0 focus-visible:before:translate-x-0 motion-reduce:before:transition-none">Wipe fill</button>
<button type="button" class="relative isolate cursor-pointer overflow-hidden rounded-[11px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-bold text-white focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] bg-[#101828] before:absolute before:inset-x-0 before:top-0 before:-z-10 before:h-1/2 before:-translate-y-[101%] before:bg-[#e11d48] before:transition-transform before:duration-[320ms] before:ease-[cubic-bezier(.4,0,.2,1)] hover:before:translate-y-0 focus-visible:before:translate-y-0 motion-reduce:before:transition-none after:absolute after:inset-x-0 after:bottom-0 after:-z-10 after:h-1/2 after:translate-y-[101%] after:bg-[#e11d48] after:transition-transform after:duration-[320ms] after:ease-[cubic-bezier(.4,0,.2,1)] hover:after:translate-y-0 focus-visible:after:translate-y-0 motion-reduce:after:transition-none">Split open</button>
<button type="button" class="relative isolate cursor-pointer overflow-hidden rounded-[11px] px-[30px] py-3.5 text-[.92rem] leading-[1.25] font-bold text-white focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-[#a5b4fc] bg-[#166534] bg-[linear-gradient(100deg,#166534,#15803d,#166534)] bg-[length:220%_100%] bg-left transition-[background-position] duration-[600ms] ease-[ease] hover:bg-right focus-visible:bg-right motion-reduce:transition-none">Gradient shift</button>

About this set

Four solid buttons for dark pages, each with a different hover sweep. Diagonal shine sends a skewed band of white light across an indigo-to-purple gradient. Wipe fill slides a darker blue layer in from the left behind the label. Split open closes two rose halves in from the top and bottom over a near-black button. Gradient shift moves a wide green gradient to its other end over 0.6 seconds. Every effect is a pseudo-element or a background-position change inside an overflow-hidden button, and the fills sit behind the label because the button is isolated, so the text stays readable while they move. The sweeps also run on keyboard focus, not only on hover, so keyboard users see the same response. Use them for primary actions where a small moment of motion is wanted, such as a hero call to action or a pricing button. The blue, rose and green shades are chosen so every white label reaches 4.5:1 at rest and after the sweep. The Bootstrap version pins the .btn colours through –bs-btn-* variables and adds the pseudo-elements in a small stylesheet.

What’s included

  • Diagonal shine, wipe fill, split open and gradient shift effects
  • Effects run on hover and on keyboard focus
  • Fills kept behind the label with isolation: isolate
  • White labels at 4.5:1 before and after each sweep
  • Transitions removed under prefers-reduced-motion

Accessibility

All four are native buttons named by their visible text, reachable with Tab and operated with Enter or Space. Focus draws a 2px light indigo outline outside the button and triggers the same sweep as hover. With prefers-reduced-motion set, the transitions are removed so the effects appear or disappear instantly.

Customise it

In plain CSS change the backgrounds on .sh-shine, .sh-wipe, .sh-split and .sh-shift and the sweep colours in their ::before and ::after rules. In Bootstrap the base colour is --sw-bg on each .btn-sweep-* class; in Tailwind edit the bg-, before:bg- and after:bg- utilities.