Skip to content

Shuttle Border

A point of light that runs around the edge of whatever is inside, like the shuttle of a loom crossing the threads. Wrap a card, an input or a button; give it your radius and background.

Motionv0.1.0experimentalWCAG 2.2 AAView spec

Optional, and kept apart.

Nothing here is included in a project unless you add it, and the plain component it builds on is unchanged. This effect is decoration: it is hidden from screen readers, stands still for visitors who prefer reduced motion, and can be paused. Use Still on a preview to see that version.

Pro plan

Unlimited projects, priority support and version history.

import { ShuttleBorder } from "@rdloom/react";

// The wrapper sets nothing but the light: give it your own radius, border and background.
export default function ShuttleBorderBasicExample() {
  return (
    <ShuttleBorder className="w-72 rounded-xl border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] p-5">
      <h3 className="font-semibold">Pro plan</h3>
      <p className="pt-1 text-sm text-[var(--rd-color-text-muted)]">Unlimited projects, priority support and version history.</p>
    </ShuttleBorder>
  );
}

Installation

npx rdloom add shuttle-border

Copies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes.

It also adds rdloom-motion.css next to your tokens and imports it from the tokens file, once: no setup, and no inline styles. The plain components you already use are untouched.

Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/shuttle-border.json

Works, but without upgrade tracking, and you import the motion CSS yourself.

Usage

import { ShuttleBorder } from "@rdloom/react";

<ShuttleBorder className="w-72 rounded-xl border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] p-5">
  <h3 className="font-semibold">Pro plan</h3>
  <p className="pt-1 text-sm text-[var(--rd-color-text-muted)]">Unlimited projects, priority support and version history.</p>
</ShuttleBorder>

Colors

Healthy
Thicker light
import { ShuttleBorder } from "@rdloom/react";

export default function ShuttleBorderColorsExample() {
  const card = "w-56 rounded-xl border border-[var(--rd-color-border-default)] bg-[var(--rd-color-surface-default)] p-4 text-sm";
  return (
    <div className="flex flex-wrap gap-4">
      <ShuttleBorder className={card} color="var(--rd-color-feedback-success)" duration={5}>
        Healthy
      </ShuttleBorder>
      <ShuttleBorder className={card} color="var(--rd-color-feedback-info)" borderWidth={2.5}>
        Thicker light
      </ShuttleBorder>
    </div>
  );
}

API Reference

Defined by the spec. Components also accept the props of the React Aria component they wrap.

PropTypeDefault
childrenrequired

The content the light runs around.

nodenone
color

Colour of the light, any CSS colour. Defaults to the action colour.

stringnone
duration

Seconds for one lap.

number4
borderWidth

Thickness of the light in pixels.

number1.5
isPaused

Stops the animation. Moving content that lasts more than five seconds should be stoppable.

booleanfalse
className

Classes for the wrapper: set its radius, background and border here.

stringnone

Accessibility

Role none, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.

Keyboard

Not interactive.

Screen readers announce

  • Silent: the wrapper adds nothing to what is read

What your code must do

  • Decoration only: the animated layers are hidden from assistive technology and never take focus
  • Stands still for visitors who ask for less motion (prefers-reduced-motion), and the effect can also be shown still with the rdm-still class
  • The light is drawn in a layer that ignores the pointer, so it never blocks clicks or text selection
  • The content inside is unchanged: its own contrast and focus apply
  • Does not flash: nothing changes more than three times in a second (WCAG 2.3.1)
  • Optional: it is a separate component, so projects that do not add it carry none of its code

Guidelines

Use it when

  • Highlighting one card, pricing plan or search field
  • Drawing the eye to a featured block

Avoid it when

  • Wrapping many items at once
  • Content that someone must read for a long time right next to the moving light

Don't

  • Putting it on every card in a grid
  • A duration under two seconds

Design tokens

The semantic tokens this component uses. Change them once and every component follows; see Design tokens.

  • --rd-color-action-primary
  • --rd-color-feedback-warning
  • --rd-color-feedback-info
  • --rd-color-surface-default
  • --rd-color-border-default
  • --rd-radius-overlay