Studio KitInteractive Buttons and Controlsmotion/react

SlideFillButton

Outline button whose solid fill slides in from a chosen edge on hover or keyboard focus while the label inverts color, with configurable fill color and timing.

Preview

Customize

6 props
0.35

Installation

Copies the component source and its CSS Module into your project.

npx motionfoundry add slide-fill-button

Writes components/ui/slide-fill-button.tsx and components/styles/slide-fill-button.module.css.

Usage

Updates live as you change the controls — only props that differ from the defaults are included.

example.tsx
1import { SlideFillButton } from "@motionfoundry/ui";2 3export function Example() {4  return (5    <SlideFillButton />6  );7}

Props

PropTypeDefault
label

Button label (ignored when children are provided).

string'Get started'
children

Custom button content.

React.ReactNodeundefined
direction

Edge the fill slides in from.

'left' | 'right' | 'up' | 'down''left'
fillColor

Fill color.

string'#f4f4f6'
fillTextColor

Label color while filled.

string'#09090b'
duration

Fill transition duration in seconds.

number0.35
disabled

Disables the button.

booleanfalse
onClick

Click handler.

(event: React.MouseEvent<HTMLButtonElement>) => voidundefined
className

Extra class merged onto the button.

string''

Accessibility

Semantic <button type="button">; the fill layer is aria-hidden and decorative. The fill also plays on :focus-visible keyboard focus (not on mouse focus), touch taps do not leave a sticky fill, and prefers-reduced-motion makes the fill and color change instant.

Toggle prefers-reduced-motion in your OS or browser devtools (Rendering → Emulate CSS media feature) to verify the reduced-motion state.

Source

components/ui/slide-fill-button.tsx
1"use client";2 3import * as React from "react";4import { motion, useReducedMotion } from "motion/react";5import styles from "../styles/slide-fill-button.module.css";6 7export interface SlideFillButtonProps {8  /** Button label (ignored when children are provided) */9  label?: string;10  /** Custom button content */11  children?: React.ReactNode;12  /** Edge the fill slides in from */13  direction?: "left" | "right" | "up" | "down";14  /** Fill color */15  fillColor?: string;16  /** Label color while filled */17  fillTextColor?: string;18  /** Fill transition duration in seconds */19  duration?: number;20  /** Disable the button */21  disabled?: boolean;22  /** Click handler */23  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;24  /** Optional className */25  className?: string;26}27 28const OFFSETS: Record<NonNullable<SlideFillButtonProps["direction"]>, { x: string; y: string }> = {29  left: { x: "-101%", y: "0%" },30  right: { x: "101%", y: "0%" },31  up: { x: "0%", y: "101%" },32  down: { x: "0%", y: "-101%" },33};34 35export function SlideFillButton({36  label = "Get started",37  children,38  direction = "left",39  fillColor = "#f4f4f6",40  fillTextColor = "#09090b",41  duration = 0.35,42  disabled = false,43  onClick,44  className = "",45}: SlideFillButtonProps) {46  const prefersReducedMotion = useReducedMotion();47  const [hovered, setHovered] = React.useState(false);48  const [focused, setFocused] = React.useState(false);49  const active = !disabled && (hovered || focused);50  const from = OFFSETS[direction] ?? OFFSETS.left;51 52  const handleFocus = (event: React.FocusEvent<HTMLButtonElement>) => {53    let visible = true;54    try {55      visible = event.currentTarget.matches(":focus-visible");56    } catch {57      visible = true;58    }59    setFocused(visible);60  };61 62  const transition = prefersReducedMotion63    ? { duration: 0 }64    : { duration, ease: [0.22, 1, 0.36, 1] as const };65 66  return (67    <button68      type="button"69      disabled={disabled}70      onClick={onClick}71      onPointerEnter={(e) => e.pointerType !== "touch" && setHovered(true)}72      onPointerLeave={() => setHovered(false)}73      onFocus={handleFocus}74      onBlur={() => setFocused(false)}75      data-active={active || undefined}76      style={77        {78          "--mf-sf-fill": fillColor,79          "--mf-sf-fill-text": fillTextColor,80          "--mf-sf-duration": `${prefersReducedMotion ? 0 : duration}s`,81        } as React.CSSProperties82      }83      className={`${styles.root} ${className}`.trim()}84    >85      <motion.span86        aria-hidden="true"87        className={styles.fill}88        initial={false}89        animate={active ? { x: "0%", y: "0%" } : from}90        transition={transition}91      />92      <span className={styles.label}>{children ?? label}</span>93    </button>94  );95}