COMPONENT / terminal-button
Terminal Button_
Terminal prompt button with a blinking block cursor that inverts to a signal fill on hover. Requires @cybr/tokens and @cybr/effects.
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/terminal-button
If shadcn reports Unknown registry "@cybr", step 1 is missing from that project's components.json.
Preview
registry/ui/terminal-button.tsx
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
// Terminal prompt button: `> label` plus a blinking block cursor, inverting to a
// signal fill on hover. Prompt and cursor are aria-hidden, so the accessible
// name is the label alone.
export function TerminalButton({
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-7",
"font-mono text-[0.9375rem] font-normal tracking-[0.04em]",
"border-(length:--border-hair) border-solid border-line bg-ground-void text-signal",
"enabled:hover:border-signal enabled:hover:bg-signal enabled:hover:text-ground-void",
"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>
<span aria-hidden="true">{"> "}</span>
{children}
</span>
<span aria-hidden="true" className="inline-block h-4.5 w-2.5 bg-current group-enabled:cursor-blink" />
</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, shown after a "> " prompt and before the block cursor. Not uppercased. |
| className | string | — | Extra classes, merged after the defaults with cn(). |
| disabled | boolean | false | Native disabled. Shows the dashed offline style with a solid, non-blinking cursor. |
| ...props | ComponentProps<"button"> | — | Every other native <button> attribute, ref and event handler is forwarded. |
Usage
Needs both stylesheets from @cybr/tokens and @cybr/effects. After the install, import them in app/globals.css:
@import "tailwindcss";
@import "../styles/cybr-tokens.css";
@import "../styles/cybr-effects.css";
import { TerminalButton } from "@/components/ui/terminal-button";
export function Action() {
return <TerminalButton onClick={() => console.log("run")}>execute</TerminalButton>;
}
States
- Idle:
--ground-voidfill,--signallabel after a>prompt, 1px--lineborder, and a block cursor blinking once a second. - Hover: inverts to a
--signalfill with--ground-voidtext and a--signalborder. - Pressed: the button presses in 3px (
--offset-hard-hover) on both axes, without a transition. - Disabled:
--surfacefill,--labeltext, 2px dashed--dimborder; the cursor stays solid. - 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
>prompt and the cursor arearia-hidden, so the accessible name is the label alone. - The cursor blinks at 1Hz over a small area, under the 3-flashes-per-second limit (WCAG 2.3.1). Under
prefers-reduced-motionit is a solid block. --signalon--ground-voidis 5.74:1 in both directions (idle and hover). If you override colours withclassName, keep text contrast at 4.5:1 or above.