COMPONENT / neon-edge-card
Neon Edge Card_
Link card whose border lights up in signal with a tight glow on hover and keyboard focus. Requires @cybr/tokens.
Install
1. Map the
@cybrnamespace, once per project. Add this inside yourcomponents.json, or into its existing"registries"object. Nocomponents.jsonyet? Runnpx shadcn@latest initfirst to create one."registries": { "@cybr": "https://cybr-ui.com/r/{name}.json" }2. Add components:
$ npx shadcn@latest add @cybr/neon-edge-card
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
registry/ui/neon-edge-card.tsx
import type { ComponentProps, ReactNode } from "react";
import { cn } from "@/lib/utils";
// Link card whose border lights up in signal with a tight glow on hover and
// keyboard focus. The one place the system allows a glow on a card.
export function NeonEdgeCard({
eyebrow,
title,
cta,
className,
...props
}: Omit<ComponentProps<"a">, "title" | "children"> & {
eyebrow?: ReactNode;
title: ReactNode;
cta?: ReactNode;
}) {
return (
<a
className={cn(
"flex min-h-48 flex-col justify-between gap-4 border-(length:--border-base) border-solid border-line bg-ground-deep p-4.5 font-mono text-ink no-underline",
"transition-[border-color,box-shadow] duration-(--duration-fast) ease-step-3 motion-reduce:transition-none",
"hover:border-signal hover:shadow-[0_0_0_1px_var(--signal),0_0_24px_-4px_var(--signal)]",
"focus-visible:border-signal focus-visible:shadow-[0_0_0_1px_var(--signal),0_0_24px_-4px_var(--signal)]",
"focus-visible:outline-(length:--focus-width) focus-visible:outline-offset-(--focus-offset) focus-visible:outline-solid focus-visible:outline-signal",
className,
)}
{...props}
>
{eyebrow ? <span className="text-caption tracking-label text-label">{eyebrow}</span> : null}
<span className="font-display text-[1.625rem] [line-height:0.95] uppercase">{title}</span>
{cta ? <span className="text-caption tracking-label text-signal">{cta}</span> : null}
</a>
);
}
Repo source. The shadcn CLI rewrites import aliases to match your project.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | Card title, set in the display face. |
| eyebrow | ReactNode | — | Small mono line at the top. |
| cta | ReactNode | — | Signal-coloured call to action at the bottom. |
| href | string | — | Link target. The whole card is one link. |
| ...props | Omit<ComponentProps<"a">, "title" | "children"> | — | Every other native <a> attribute is forwarded; className is merged last with cn(). |
Usage
Needs the stylesheet from @cybr/tokens. After the install, import it in app/globals.css:
@import "tailwindcss";
@import "../styles/cybr-tokens.css";
import { NeonEdgeCard } from "@/components/ui/neon-edge-card";
export function Next() {
return <NeonEdgeCard href="/docs/components" eyebrow="NEXT" title="Component index" cta="OPEN →" />;
}
States
- Idle: 2px
--lineborder on--ground-deep. - Hover: the border turns
--signaland gains a 1px ring and a tight 24px--signalglow, stepping in over 120ms onsteps(3). - Focus-visible: the same glow, plus a 3px solid
--signaloutline offset 6px. - Reduced motion: the glow snaps on with no transition.
- The artboard shows the lit state on a static poster. The component has no forced-lit prop; hover or tab to it to see it.
Accessibility
- The whole card is one native
<a>, so its accessible name is its full text. Keep the text short. - It renders
<a>. For client-side routing, wrap it in your router's link pattern or copy the classes onto your own link. - The glow is a large, low-contrast change and runs once per hover, not repeatedly.
--ink,--labeland--signaltext sit on--ground-deep, darker than--bg, so they keep at least the overview's figures on--bg. If you override colours withclassName, keep text contrast at 4.5:1 or above.