Buttons

Border Trace Button

A native button whose token-colored border segment traces the control on hover or keyboard focus.

Preview in your theme

Loading preview…

import * as React from "react"
import { cn } from "@/lib/utils"

/**
 * The dash is expressed against pathLength=1, so the same timing works for
 * every button width. React 19 hoists and deduplicates this style by href.
 */
const KEYFRAMES = `@keyframes zbt-trace{from{stroke-dashoffset:0}to{stroke-dashoffset:-1}}`

export interface BorderTraceButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  /** Seconds for one lap of the highlight segment. */
  duration?: number
}

Installation

npx shadcn@latest add https://ui.zyeon.ai/r/border-trace-button.json

Prompt

Build a React + TypeScript + Tailwind "BorderTraceButton" component with no
animation dependency.

Contract
- Export BorderTraceButton with React.forwardRef<HTMLButtonElement>.
- Extend React.ButtonHTMLAttributes<HTMLButtonElement>, add duration?: number
  in seconds, and default duration to 1.6.
- Merge className with cn(), merge consumer style after the internal animation
  custom property, spread every remaining native button prop, and default
  type="button" while allowing the consumer to override it.

Behavior
- Keep a normal semantic border visible at all times.
- Overlay a decorative SVG rect with pathLength="1". Give it a short dash and
  a long gap so its timing is independent of the button's rendered dimensions.
- Park the dash animation until the button is hovered or keyboard-focused;
  then reveal it and run repeated laps. Disabled remains a native disabled
  button and cannot start the interaction.
- Clamp non-finite or extremely short duration values to a safe fallback.

Rendering & styling
- Use only semantic tokens: border-border, bg-background, bg-muted,
  text-foreground, stroke-primary and ring-ring. Do not use hex colors,
  fixed shadows, or a fixed corner radius.
- Read the host radius through rounded-md / var(--radius-md); the SVG follows
  that token rather than owning a separate visual radius.
- Mark the SVG aria-hidden and focusable=false. The button owns the accessible
  name through its children and preserves native keyboard/click behavior.
- Use motion-reduce:hidden for the moving SVG layer and
  motion-reduce:transition-none for cosmetic transitions. The static border
  still communicates the boundary when animation is disabled.
- Ship the keyframe in a React 19 hoisted <style href precedence> tag so the
  registry item needs no Tailwind config edit and duplicate instances dedupe.

Customization levers
- Tempo: change duration per call site; leave the normalized dash geometry
  alone so wide and narrow buttons keep the same lap timing.
- Trace length: tune the two strokeDasharray fractions while keeping their sum
  at 1. A shorter first fraction reads as a quick spark; a longer one reads as
  a progressing outline.
- Emphasis: swap stroke-primary for stroke-accent or another semantic token,
  and adjust hover background opacity without changing the native contract.
- Density: override padding through className, or add a documented cva size
  axis when the product needs a shared small/medium/large scale.
- Trigger: keep hover and focus paired; if product behavior calls for an
  always-running trace, change only animation-play-state and still preserve the
  reduced-motion branch.

Concepts

  • Normalized path timingpathLength="1" turns every perimeter into the same zero-to-one coordinate system, so a compact toolbar button and a wide CTA complete a lap in the same duration.
  • Progressive motion — the trace is parked until intent is visible through hover or keyboard focus; idle interfaces do not spend attention on a permanently moving decoration.
  • Semantic fallback — the ordinary border-border outline is the durable boundary. Reduced motion removes only the traveling highlight, never the control or its affordance.
  • Native contract preservation — the component forwards its ref and every button attribute, so form type, disabled state, analytics handlers and accessible naming remain consumer-owned.
  • Trigger parity — hover and focus-visible start the same visual response, preventing a pointer-only flourish from becoming the sole indicator of emphasis.

On This Page