Buttons
GhostShift
An outline button whose label rolls up and out while a second label rolls in from below. CSS only, no dependencies.
Download
- React
- Tailwind CSS v4
components/ui/ghost-shift.tsx
import type { ReactNode } from "react";
type GhostShiftProps = {
children: ReactNode;
className?: string;
onClick?: () => void;
/** Label that rolls in on hover. Defaults to the same label. */
hoverLabel?: ReactNode;
/** Seconds the roll takes. */
duration?: number;
};
/* GhostShift: a ghost (outline) button whose label rolls up and out while a second label rolls in from below. CSS only. */
export function GhostShift({
children,
className = "rounded-full border border-[#a78bfa]/60 px-9 py-4 text-[15px] font-medium text-[#ede9fe] transition-colors duration-300 hover:bg-[#a78bfa] hover:text-[#1a1530] focus-visible:bg-[#a78bfa] focus-visible:text-[#1a1530]",
onClick,
hoverLabel,
duration = 0.35,
}: GhostShiftProps) {
const roll = "[grid-area:1/1] transition-transform ease-[cubic-bezier(0.22,1,0.36,1)] motion-reduce:transition-none";
return (
<button type="button" onClick={onClick} className={`group ${className}`}>
<span className="grid overflow-hidden">
<span className={`${roll} group-hover:-translate-y-full group-focus-visible:-translate-y-full`} style={{ transitionDuration: `${duration}s` }}>
{children}
</span>
<span aria-hidden className={`${roll} translate-y-full group-hover:translate-y-0 group-focus-visible:translate-y-0`} style={{ transitionDuration: `${duration}s` }}>
{hoverLabel ?? children}
</span>
</span>
</button>
);
}
1. Paste the Code tab into
components/ui/ghost-shift.tsx. The styles are Tailwind CSS v4 classes.2. Use it. This is the preview above:
app/ghost-shift-demo.tsx
import { GhostShift } from "@/components/ui/ghost-shift"; export default function GhostShiftExample() { return ( <div className="grid h-full place-items-center bg-[radial-gradient(circle_at_50%_60%,#1d1a33,#0d0c14_70%)]"> <GhostShift hoverLabel="Let's talk">Contact us</GhostShift> </div> ); }
Props
| Name | Type | Default | What it does |
|---|---|---|---|
| children | ReactNode | — | Button label. |
| onClick | () => void | — | Click handler. |
| hoverLabel | ReactNode | same label | Label that rolls in. |
| duration | number | 0.35 | Seconds the roll takes. |
| className | string | violet outline pill that fills on hover | Replaces the default look. |