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.
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.
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-borderCopies 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.jsonWorks, 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
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.
| Prop | Type | Default |
|---|---|---|
childrenrequiredThe content the light runs around. | node | none |
colorColour of the light, any CSS colour. Defaults to the action colour. | string | none |
durationSeconds for one lap. | number | 4 |
borderWidthThickness of the light in pixels. | number | 1.5 |
isPausedStops the animation. Moving content that lasts more than five seconds should be stoppable. | boolean | false |
classNameClasses for the wrapper: set its radius, background and border here. | string | none |
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