whiskeyjack
uibeamsource: ok · hit
Beam
A travelling light around an element's edge, for the one thing on a screen that needs the eye.
npx shadcn add "https://whiskeyjack.net/r/beam.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightRegistry dependencies
https://whiskeyjack.net/r/utils.json
Files1 · source cached 10/9/2026, 11:29:37 AM
components/ui/beam.tsx
import * as React from "react";
import { cn } from "@/lib/utils";
export interface BeamProps extends React.HTMLAttributes<HTMLDivElement> {
/** Runs the beam. Off, the wrapper is inert and costs nothing visually. */
active?: boolean;
/**
* Which urgency the light carries. `accent` is a nudge, `warning` a
* deadline approaching, `error` one about to pass.
*/
tone?: "accent" | "warning" | "error";
/**
* Seconds for one full turn. Slower reads as calmer; below ~2s it starts to
* feel like a loading spinner rather than a highlight.
*/
durationSec?: number;
children: React.ReactNode;
}
/**
* Draws a travelling light around whatever it wraps, for the one thing on a
* screen that needs the eye. Built for a `Card` -- a goal about to break its
* streak, a check-in whose window is closing -- but it wraps anything with a
* border radius.
*
* ```tsx
* <Beam active={goal.atRisk} tone="warning" className="rounded-xl">
* <Card>...</Card>
* </Beam>
* ```
*
* **Match the radius.** The layers inherit `border-radius` from this wrapper,
* which defaults to `rounded-xl` (a `Card`'s). Pass a `className` with the
* child's radius when it differs, or the light traces a shape the content does
* not have.
*
* **The beam carries no meaning on its own.** It is decoration drawn in
* pseudo-elements, invisible to assistive tech, and tone is colour alone --
* so whatever makes this element urgent has to be in its text too. Reduced
* motion holds the light still rather than removing it, which keeps the cue
* for anyone who cannot use the movement.
*
* **One at a time.** Two of these on a screen compete, and the effect is worth
* nothing once it is not the only one.
*/
const Beam = React.forwardRef<HTMLDivElement, BeamProps>(
(
{ active = false, tone = "accent", durationSec, className, style, children, ...props },
ref,
) => (
<div
ref={ref}
className={cn("wj-beam rounded-xl", className)}
data-active={active}
data-tone={tone}
style={
durationSec === undefined
? style
: ({ "--wj-beam-duration": `${durationSec}s`, ...style } as React.CSSProperties)
}
{...props}
>
{children}
</div>
),
);
Beam.displayName = "Beam";
export { Beam };