Buttons

Curtain Button

A red curtain button, closed by default; on hover both panels sweep open to the sides revealing an outlined red label.

Loading preview
Advertisement
Ad space reserved for a future placement

Source code

"use client";

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

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

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

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

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

Usage

Import the component and use it like this.

import { CurtainButton } from "./CurtainButton";

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

Dependencies

No external dependencies.

Usage notes

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