Mischief

04 / Controls

Shift Button

A call to action that trades its leading icon for a directional cue when someone approaches it.

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/shift-button
import { ShiftButton } from "mischief-ui/shift-button"
Also installs
  • @base-ui/react
  • lucide-react

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/shift-button/shift-button.tsx
"use client" import * as React from "react"import { Button } from "@base-ui/react/button"import { ArrowRight } from "lucide-react"import { cn } from "@/lib/utils" export interface ShiftButtonProps extends Omit<  React.ComponentPropsWithoutRef<typeof Button>,  "children"> {  children: React.ReactNode  leadingIcon: React.ReactNode  trailingIcon?: React.ReactNode

Usage

export function DownloadButton() {
  return (
    <ShiftButton
      render={<a href="/download" />}
      leadingIcon={<Apple aria-hidden="true" />}
    >
      Download for Mac
    </ShiftButton>
  )
}

The shift

On hover the leading icon slides out to the left and fades, while the trailing icon slides in from the right to take its place. The grid keeps a fixed column for each, so the label never moves and the button never changes width -- the motion happens inside a stable shape.

Under reduced motion the trailing icon is not shown at all and the leading icon stays exactly where it is. The button is then simply a button with an icon, which is the point: the shift is decoration, and nothing is communicated by it alone.

Give trailingIcon only when it says something -- an arrow for navigation, a check for a completed action. Leaving it out is fine, and the leading icon then stays put for everyone.

What it needs installed

Base UI supplies the button, which is why this component is imported from its own entry rather than the package root, and why @base-ui/react has to be installed alongside.

npm install mischief-ui @base-ui/react

API

childrenReactNodeThe button or link label.
leadingIconReactNodeThe icon visible at rest.
trailingIconReactNodeThe arriving icon. Defaults to an arrow.
renderReactElementRenders another element, such as a link.
classNamestringClasses for the root element.

Accessibility

Base UI preserves native button behavior and supports rendering a real link for navigation. The label never disappears, focus remains visible, and reduced motion keeps both the leading icon and text still.