Buttons

Slide Fill Button

A clean outlined button whose background slides in left to right on hover, revealing a solid fill behind the label.

Loading preview
Advertisement
Ad space reserved for a future placement

Source code

"use client";

import type { ButtonHTMLAttributes, ReactNode } from "react";

type SlideFillButtonSize = "sm" | "md" | "lg";

interface SlideFillButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "color"> {
  children: ReactNode;
  accentColor?: string;
  size?: SlideFillButtonSize;
}

const SIZE_CLASSES: Record<SlideFillButtonSize, string> = {
  sm: "px-4 py-2 text-sm",
  md: "px-6 py-3 text-base",
  lg: "px-8 py-4 text-lg",
};

export function SlideFillButton({
  children,
  accentColor = "#3f3cf2",
  size = "md",
  className,
  style,
  ...props
}: SlideFillButtonProps) {
  return (
    <button
      type="button"
      className={["slide-fill-button", SIZE_CLASSES[size], className].filter(Boolean).join(" ")}
      style={{ ...style, ["--slide-accent" as string]: accentColor }}
      {...props}
    >
      <span className="slide-fill-button-fill" aria-hidden="true" />
      <span className="slide-fill-button-label">{children}</span>
      <style jsx>{`
        .slide-fill-button {
          position: relative;
          display: inline-flex;
          align-items: center;
          justify-content: center;
          overflow: hidden;
          border-radius: 0.5rem;
          border: 1.5px solid var(--slide-accent);
          background-color: transparent;
          color: var(--slide-accent);
          font-weight: 600;
          cursor: pointer;
        }
        .slide-fill-button-fill {
          position: absolute;
          inset: 0;
          background-color: var(--slide-accent);
          transform: scaleX(0);
          transform-origin: left center;
          transition: transform 350ms cubic-bezier(0.16, 1, 0.3, 1);
        }
        .slide-fill-button:hover .slide-fill-button-fill,
        .slide-fill-button:focus-visible .slide-fill-button-fill {
          transform: scaleX(1);
        }
        .slide-fill-button-label {
          position: relative;
          transition: color 350ms cubic-bezier(0.16, 1, 0.3, 1);
        }
        .slide-fill-button:hover .slide-fill-button-label,
        .slide-fill-button:focus-visible .slide-fill-button-label {
          color: white;
        }
        .slide-fill-button:focus-visible {
          outline: 2px solid var(--slide-accent);
          outline-offset: 2px;
        }
        .slide-fill-button:disabled {
          cursor: not-allowed;
          opacity: 0.5;
        }
        @media (prefers-reduced-motion: reduce) {
          .slide-fill-button-fill,
          .slide-fill-button-label {
            transition: none;
          }
        }
      `}</style>
    </button>
  );
}

Usage

Import the component and use it like this.

import { SlideFillButton } from "./SlideFillButton";

export default function Example() {
  return (
    <SlideFillButton accentColor="#3f3cf2" size="md">
      Get started
    </SlideFillButton>
  );
}

Dependencies

No external dependencies.

Usage notes

Pass an accentColor hex to change the outline/fill color, and size (sm/md/lg) to change padding.