COMPONENT / hud-bracket-button
HUD Bracket Button_
Dark button framed by HUD corner brackets that snap out 5px on hover. 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-bracket-button
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
registry/ui/hud-bracket-button.tsx
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
const CORNER =
"pointer-events-none absolute size-3 border-signal transition-[top,right,bottom,left] duration-(--duration-fast) ease-step-3 motion-reduce:transition-none group-disabled:border-dim";
// Dark button framed by four signal-coloured corner brackets that snap out 5px
// on hover. The brackets are decoration, hidden from assistive tech.
export function HudBracketButton({
className,
type = "button",
children,
...props
}: ComponentProps<"button">) {
return (
<button
type={type}
className={cn(
"group relative inline-flex min-h-13 cursor-pointer items-center justify-center gap-2.5 rounded-none px-8.5",
"font-mono text-[0.9375rem] font-bold uppercase tracking-button",
"border-(length:--border-hair) border-solid border-line bg-ground-deep text-ink",
"enabled:hover:text-signal enabled:active:translate-(--offset-hard-hover)",
"focus-visible:outline-(length:--focus-width) focus-visible:outline-offset-(--focus-offset) focus-visible:outline-solid focus-visible:outline-signal",
"disabled:cursor-not-allowed disabled:border-(length:--border-base) disabled:border-dashed disabled:border-dim disabled:bg-surface disabled:text-label",
className,
)}
{...props}
>
<span
aria-hidden="true"
className={cn(
CORNER,
"-top-1.5 -left-1.5 border-t-(length:--border-heavy) border-l-(length:--border-heavy)",
"group-enabled:group-hover:-top-2.75 group-enabled:group-hover:-left-2.75",
)}
/>
<span
aria-hidden="true"
className={cn(
CORNER,
"-top-1.5 -right-1.5 border-t-(length:--border-heavy) border-r-(length:--border-heavy)",
"group-enabled:group-hover:-top-2.75 group-enabled:group-hover:-right-2.75",
)}
/>
<span
aria-hidden="true"
className={cn(
CORNER,
"-bottom-1.5 -left-1.5 border-b-(length:--border-heavy) border-l-(length:--border-heavy)",
"group-enabled:group-hover:-bottom-2.75 group-enabled:group-hover:-left-2.75",
)}
/>
<span
aria-hidden="true"
className={cn(
CORNER,
"-right-1.5 -bottom-1.5 border-r-(length:--border-heavy) border-b-(length:--border-heavy)",
"group-enabled:group-hover:-right-2.75 group-enabled:group-hover:-bottom-2.75",
)}
/>
{children}
</button>
);
}
Repo source. The shadcn CLI rewrites import aliases to match your project.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| type | "button" | "submit" | "reset" | "button" | Native button type. Defaults to button so it never submits a form by accident. |
| children | ReactNode | — | Label text. |
| className | string | — | Extra classes, merged after the defaults with cn(). |
| disabled | boolean | false | Native disabled. Shows the dashed offline style; brackets turn --dim and stay put. |
| ...props | ComponentProps<"button"> | — | Every other native <button> attribute, ref and event handler is forwarded. |
Usage
Needs the stylesheet from @cybr/tokens. After the install, import it in app/globals.css after @import "tailwindcss";.
import { HudBracketButton } from "@/components/ui/hud-bracket-button";
export function Action() {
return <HudBracketButton onClick={() => console.log("lock")}>Target</HudBracketButton>;
}
States
- Idle:
--ground-deepfill,--inklabel, 1px--lineborder, four 12px--signalcorner brackets sitting 6px outside the box. - Hover: label turns
--signaland the brackets snap out a further 5px onsteps(3)over 120ms. - Pressed: the button presses in 3px (
--offset-hard-hover) on both axes, without a transition. - Disabled:
--surfacefill,--labeltext, 2px dashed--dimborder; brackets turn--dimand do not move. - Focus-visible: 3px solid
--signaloutline, offset 6px.
Accessibility
- Renders a native
<button>, so keyboard activation, focus and the button role come for free. typedefaults tobutton, so it never submits a surrounding form unless you passtype="submit".- Keyboard focus shows a bold
--signaloutline offset from the button. disableduses the native attribute: it leaves the tab order and is announced as unavailable. Hover and press effects are switched off while disabled.- The four brackets are
aria-hiddendecoration; the accessible name is the label. - Under
prefers-reduced-motionthe brackets still move on hover, but jump instead of stepping. - Label text is
--ink(and--signalon hover) on--ground-deep. If you override colours withclassName, keep text contrast at 4.5:1 or above.