Hover Edge
Hover edge accent: a thin rose and amber line on any edge of a positioned parent, as used on cascade Card examples. It appears on parent hover or keyboard focus, can mark a selected item, and honors reduced motion.
pnpm dlx shadcn add @01orbital/hover-edgeimport type { ComponentProps } from "react"
import { cn } from "cn"
const EDGES = {
top: "inset-x-0 top-0 h-0.5",
bottom: "inset-x-0 bottom-0 h-0.5",
left: "inset-y-0 left-0 w-0.5",
right: "inset-y-0 right-0 w-0.5",
} as const
// React hoists one copy of this stylesheet for every instance.
const INTERACTION_CSS = `@media(hover:hover){:hover>.orbital-hover-edge{opacity:1}}:focus-visible>.orbital-hover-edge,:has(:is(a,button,summary,[role=button]):focus-visible)>.orbital-hover-edge,.orbital-hover-edge[data-active]{opacity:1}`
/**
* A two-pixel rose–amber edge, fading out at both ends. Place as
* a direct child of a positioned element: it appears when that parent is
* hovered, has keyboard focus, or holds a link or button with keyboard focus.
* Focus in a form field inside the parent leaves it off. No hover group or
* event handlers are required. Reduced motion removes the opacity transition.
* The line is decorative, ignores the pointer and uses a span root so it can
* sit inside buttons and links as well as larger containers.
*/
function HoverEdge({
edge = "bottom",
active = false,
className,
style,
...props
}: Omit<ComponentProps<"span">, "children"> & {
/** The edge carrying the line. Defaults to `bottom`. */
edge?: keyof typeof EDGES
/** Keep the line visible, for example on a selected item. Defaults to false. */
active?: boolean
}) {
const direction =
edge === "left" || edge === "right" ? "to bottom" : "to right"
return (
<>
<style href="01orbital-hover-edge" precedence="default">
{INTERACTION_CSS}
</style>
<span
aria-hidden="true"
data-slot="hover-edge"
data-edge={edge}
data-active={active || undefined}
className={cn(
"orbital-hover-edge pointer-events-none absolute opacity-0 transition-opacity duration-400 motion-reduce:transition-none",
EDGES[edge],
className
)}
style={{
background: `linear-gradient(${direction}, transparent, oklch(0.65 0.1 350) 20%, oklch(0.62 0.13 60) 50%, oklch(0.45 0.12 40) 80%, transparent)`,
...style,
}}
{...props}
/>
</>
)
}
export { HoverEdge }
import { HoverEdge } from "@/components/glow/hover-edge"
import { Button } from "@/components/ui/button"
export default function HoverEdgeDemo() {
return (
<div className="flex flex-wrap items-center justify-center gap-6">
<Button
variant="outline"
size="lg"
className="relative overflow-hidden rounded-md"
>
Hover or focus
<HoverEdge />
</Button>
<Button
variant="outline"
size="lg"
className="relative overflow-hidden rounded-md"
>
A selected item
<HoverEdge edge="top" active />
</Button>
</div>
)
}
The example also uses button. Install it too:
pnpm dlx shadcn add @01orbital/buttonnpx shadcn add @01orbital/buttonyarn shadcn add @01orbital/buttonbunx --bun shadcn add @01orbital/buttonUsage
import { HoverEdge } from "@/components/glow/hover-edge"
import { Button } from "@/components/ui/button"
export default function HoverEdgeDemo() {
return (
<div className="flex flex-wrap items-center justify-center gap-6">
<Button
variant="outline"
size="lg"
className="relative overflow-hidden rounded-md"
>
Hover or focus
<HoverEdge />
</Button>
<Button
variant="outline"
size="lg"
className="relative overflow-hidden rounded-md"
>
A selected item
<HoverEdge edge="top" active />
</Button>
</div>
)
}Examples
New
hover-edge-buttons
New
hover-edge-tabs
New
hover-edge-field
API reference
A two-pixel rose–amber edge, fading out at both ends. Place as a direct child of a positioned element: it appears when that parent is hovered, has keyboard focus, or holds a link or button with keyboard focus. Focus in a form field inside the parent leaves it off. No hover group or event handlers are required. Reduced motion removes the opacity transition. The line is decorative, ignores the pointer and uses a span root so it can sit inside buttons and links as well as larger containers.
Prop
Type
It also accepts the HTML attributes of the element it renders.
Dependencies
npm: cn. The CLI installs them for you.
No other registry pieces.