COMPONENT / hardware-panel
Hardware Panel_
Riveted surface panel with a cable channel and status rows that pair ON/OFF text with a signal LED. 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/hardware-panel
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
PWR / BUS 04
MAINON
AUXOFF
UPLINKON
registry/ui/hardware-panel.tsx
import type { ComponentProps, ReactNode } from "react";
import { cn } from "@/lib/utils";
const RIVET =
"pointer-events-none absolute size-2.25 rounded-full bg-dim shadow-[inset_1px_1px_0_var(--label)]";
// Riveted surface panel with a cable channel down the left edge. Stack
// HardwarePanelStatus rows (or anything else) as children.
export function HardwarePanel({
title,
titleAs: Title = "h3",
className,
children,
...props
}: Omit<ComponentProps<"section">, "title"> & {
title: ReactNode;
titleAs?: "h2" | "h3" | "h4";
}) {
return (
<section
className={cn(
"relative border-(length:--border-base) border-solid border-dim bg-surface pt-6 pr-5.5 pb-5.5 pl-10 font-mono text-ink",
className,
)}
{...props}
>
<span aria-hidden="true" className={cn(RIVET, "top-2 left-2")} />
<span aria-hidden="true" className={cn(RIVET, "top-2 right-2")} />
<span aria-hidden="true" className={cn(RIVET, "bottom-2 left-2")} />
<span aria-hidden="true" className={cn(RIVET, "right-2 bottom-2")} />
<span
aria-hidden="true"
className="pointer-events-none absolute top-6.5 bottom-6.5 left-3.5 w-3 border-(length:--border-hair) border-solid border-bg bg-[repeating-linear-gradient(0deg,var(--bg)_0_4px,var(--line)_4px_6px)]"
/>
<Title className="mb-4.5 font-display text-[1.375rem] [line-height:1]">{title}</Title>
<div className="flex flex-col gap-2.5">{children}</div>
</section>
);
}
// One status row. The state is always spelled out in text; the LED only
// repeats it visually.
export function HardwarePanelStatus({
label,
on,
stateLabel,
className,
...props
}: Omit<ComponentProps<"div">, "children"> & {
label: ReactNode;
on: boolean;
stateLabel?: ReactNode;
}) {
return (
<div
className={cn("flex items-center justify-between gap-4 text-caption tracking-[0.12em]", className)}
{...props}
>
<span>{label}</span>
<span className="flex items-center gap-2">
{stateLabel ?? (on ? "ON" : "OFF")}
<span
aria-hidden="true"
className={cn(
"size-2.5",
on
? "bg-signal shadow-[0_0_10px_var(--signal)]"
: "border-(length:--border-base) border-solid border-label",
)}
/>
</span>
</div>
);
}
Repo source. The shadcn CLI rewrites import aliases to match your project.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | Panel title, set in the display face. |
| titleAs | "h2" | "h3" | "h4" | "h3" | Heading level for the title. Match it to the page outline. |
| children | ReactNode | — | Panel content, stacked with a 10px gap. Usually HardwarePanelStatus rows. |
| ...props | Omit<ComponentProps<"section">, "title"> | — | Every other native <section> attribute is forwarded; className is merged last with cn(). |
| HardwarePanelStatus label | ReactNode | — | Row label, such as MAIN. |
| HardwarePanelStatus on | boolean | — | Lights the LED and sets the default state text. |
| HardwarePanelStatus stateLabel | ReactNode | "ON" | "OFF" | State text shown before the LED. Defaults from on. |
| HardwarePanelStatus ...props | Omit<ComponentProps<"div">, "children"> | — | Every other native <div> attribute is forwarded to the row. |
Usage
Needs the stylesheet from @cybr/tokens. After the install, import it in app/globals.css:
@import "tailwindcss";
@import "../styles/cybr-tokens.css";
import { HardwarePanel, HardwarePanelStatus } from "@/components/ui/hardware-panel";
export function Power({ uplink }: { uplink: boolean }) {
return (
<HardwarePanel title="PWR / BUS 04">
<HardwarePanelStatus label="MAIN" on />
<HardwarePanelStatus label="UPLINK" on={uplink} />
</HardwarePanel>
);
}
Anatomy
- Panel:
--surfacefill with a 2px--dimborder and extra left padding for the cable channel. - Rivets: four 9px round
--dimrivets with an inset--labelhighlight. Rivets are a round decorative motif; nothing interactive is rounded. - Cable channel: a 12px strip of
--bgand--linebands down the left edge. - Status row: label on the left; state text and a 10px LED on the right. On is a
--signalsquare with a small glow; off is a 2px--labeloutline.
Accessibility
- Renders a
<section>with a real heading, so it shows up as a named region in the outline. Set the level withtitleAs. - Every state is spelled out in text. The LED only repeats it, so state never depends on colour.
- Rivets, the cable channel and LEDs are decorative and hidden from assistive tech.
- Text is
--inkon--surface. If you override colours withclassName, keep text contrast at 4.5:1 or above. - Nothing animates.