A card with a soft light that follows the pointer. The light is off for touch and for visitors who ask for less motion, and the card keeps its own focus ring.
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 { SpotlightCard } from "@rdloom/react";
export default function SpotlightCardBasicExample() {
return (
<div className="flex w-full justify-center">
<SpotlightCard className="w-full max-w-sm">
<h3 className="text-base font-medium">Weekly summary</h3>
<p className="mt-1 text-sm text-[var(--rd-color-text-muted)]">Move the pointer over this card and a soft light follows it.</p>
</SpotlightCard>
</div>
);
}Installation
npx rdloom add spotlight-cardCopies 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/spotlight-card.jsonWorks, but without upgrade tracking, and you import the motion CSS yourself.
Usage
import { SpotlightCard } from "@rdloom/react";
<div className="flex w-full justify-center">
<SpotlightCard className="w-full max-w-sm">
<h3 className="text-base font-medium">Weekly summary</h3>
<p className="mt-1 text-sm text-[var(--rd-color-text-muted)]">Move the pointer over this card and a soft light follows it.</p>
</SpotlightCard>
</div>Grid
import { SpotlightCard } from "@rdloom/react";
const items = [
{ title: "Invoices", text: "Send and track what is owed." },
{ title: "Customers", text: "Everyone you bill, in one list." },
{ title: "Reports", text: "Monthly totals you can export." },
];
export default function SpotlightCardGridExample() {
return (
<div className="flex w-full justify-center">
<div className="grid w-full max-w-2xl gap-3 sm:grid-cols-3">
{items.map((item) => (
<SpotlightCard key={item.title} size={180}>
<h3 className="text-sm font-medium">{item.title}</h3>
<p className="mt-1 text-sm text-[var(--rd-color-text-muted)]">{item.text}</p>
</SpotlightCard>
))}
</div>
</div>
);
}With link
import { SpotlightCard } from "@rdloom/react";
export default function SpotlightCardWithLinkExample() {
return (
<div className="flex w-full justify-center">
<SpotlightCard className="w-full max-w-sm">
<h3 className="text-base font-medium">Billing</h3>
<p className="mt-1 text-sm text-[var(--rd-color-text-muted)]">Update your plan or payment method.</p>
<a
href="#billing"
className="mt-3 inline-block rounded-[var(--rd-radius-control)] text-sm font-medium text-[var(--rd-color-action-primary)] underline outline-none focus-visible:ring-2 focus-visible:ring-[var(--rd-color-focus-ring)]"
>
Open billing
</a>
</SpotlightCard>
</div>
);
}API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
childrenrequiredThe card content. | node | none |
sizeRadius of the light in pixels. | number | 240 |
colorColour of the light, any CSS colour. Defaults to the primary action colour. | string | none |
isPausedTurns the light off. | boolean | false |
Accessibility
Role group, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
Not interactive.
Screen readers announce
- Read as ordinary content: the light is not announced
What your code must do
- The light is decoration: aria-hidden, behind the content and never blocks the pointer
- Off for touch pointers, for reduced motion, inside rdm-still and with isPaused
- Content and its contrast do not depend on the light: the light is a faint tint under the text
- Any focusable child keeps its own visible focus ring: the card never replaces or hides it
- In forced-colors (high contrast) mode the light is removed
Guidelines
Use it when
- Feature or pricing cards
- A grid of tiles that should feel responsive to the pointer
Avoid it when
- Cards that must be perfectly flat in a dense table
Don't
- Putting text colours that only pass contrast over the brightest part of the light
- Hiding the focus ring of a link inside the card
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-default--rd-color-border-default--rd-color-action-primary--rd-radius-overlay