COMPONENT / hud-image-frame
HUD Image Frame_
Void-black media frame with signal corner brackets, a centre reticle, a static scan line, an outlined index numeral and a caption. 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/hud-image-frame
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
registry/ui/hud-image-frame.tsx
import type { ComponentProps, ReactNode } from "react";
import { cn } from "@/lib/utils";
const BRACKET = "pointer-events-none absolute size-5.5 border-signal border-solid";
// Void-black media frame with signal corner brackets, a centre reticle and a
// static scan line. Children fill the frame behind the overlays.
export function HudImageFrame({
index,
caption,
scanLine = true,
className,
children,
...props
}: ComponentProps<"figure"> & {
index?: ReactNode;
caption?: ReactNode;
scanLine?: boolean;
}) {
return (
<figure
className={cn(
"relative isolate m-0 flex min-h-49 items-end justify-between gap-4 overflow-hidden border-(length:--border-hair) border-solid border-line bg-ground-void p-4.5 font-mono",
className,
)}
{...props}
>
{children ? (
<div className="absolute inset-0 -z-10 [&>*]:size-full [&>img]:object-cover">{children}</div>
) : null}
<span aria-hidden="true" className={cn(BRACKET, "top-2.5 left-2.5 border-t-(length:--border-heavy) border-l-(length:--border-heavy)")} />
<span aria-hidden="true" className={cn(BRACKET, "top-2.5 right-2.5 border-t-(length:--border-heavy) border-r-(length:--border-heavy)")} />
<span aria-hidden="true" className={cn(BRACKET, "bottom-2.5 left-2.5 border-b-(length:--border-heavy) border-l-(length:--border-heavy)")} />
<span aria-hidden="true" className={cn(BRACKET, "right-2.5 bottom-2.5 border-r-(length:--border-heavy) border-b-(length:--border-heavy)")} />
<span
aria-hidden="true"
className="pointer-events-none absolute top-1/2 left-1/2 size-10 -translate-1/2 rounded-full border-(length:--border-hair) border-solid border-label"
/>
<span aria-hidden="true" className="pointer-events-none absolute top-[30%] bottom-[30%] left-1/2 w-px bg-label" />
<span aria-hidden="true" className="pointer-events-none absolute top-1/2 right-[42%] left-[42%] h-px bg-label" />
{scanLine ? (
<span aria-hidden="true" className="pointer-events-none absolute inset-x-0 top-[38%] h-0.5 bg-signal opacity-55" />
) : null}
{index ? (
<span
aria-hidden="true"
className="relative font-display text-[5.5rem] [line-height:0.8] text-transparent [-webkit-text-stroke:2px_var(--ink)]"
>
{index}
</span>
) : null}
{caption ? (
<figcaption className="relative ml-auto text-caption tracking-label text-label">{caption}</figcaption>
) : null}
</figure>
);
}
Repo source. The shadcn CLI rewrites import aliases to match your project.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| children | ReactNode | — | Media that fills the frame behind the overlays, such as an <img> or <video>. Images use object-fit: cover. |
| index | ReactNode | — | Large outlined numeral at the bottom left. Decorative. |
| caption | ReactNode | — | Caption at the bottom right, rendered as the <figcaption>. |
| scanLine | boolean | true | Shows the static signal scan line at 38% height. |
| ...props | ComponentProps<"figure"> | — | Every other native <figure> attribute is forwarded; className is merged last with cn(). Set height with min-h-*. |
Usage
Needs the stylesheet from @cybr/tokens. After the install, import it in app/globals.css:
@import "tailwindcss";
@import "../styles/cybr-tokens.css";
import Image from "next/image";
import { HudImageFrame } from "@/components/ui/hud-image-frame";
export function Feed() {
return (
<HudImageFrame index="01" caption="CAM 04 · SECTOR NINE">
<Image src="/sector-nine.jpg" alt="Loading bay at night" fill />
</HudImageFrame>
);
}
Anatomy
- Frame:
--ground-voidfill, 1px--lineborder, 196px minimum height. - Brackets: four 22px
--signalcorners, 3px thick, 10px in from the edges. - Reticle: a 40px
--labelring with short cross lines through the centre. - Scan line: a static 2px
--signalline at 38% height, 55% opacity. Nothing moves. - Index and caption: an 88px outlined numeral at the bottom left and a mono caption at the bottom right.
Accessibility
- Renders a
<figure>; the caption is its<figcaption>. - Give images real
alttext. Usealt=""only when the caption already says everything. - Brackets, reticle, scan line and the index numeral are decorative and hidden from assistive tech. The outlined numeral has no fill, so it makes no contrast claim; don't put meaning in it.
- The caption is
--labelon--ground-void. Over a bright image, check it stays readable.