jengacore.shadcn registry · Tailwind v4 · Radix and Base UI
Components
shadcn, restyled · @jengacore/button

Button

shadcn Button with jengacore variants, 30/34/44px heights and a `shortcut` hint. Same API as upstream.

Separate Radix and Base UI builds

Install

terminal
npx shadcn@latest add @jengacore/button

Without the namespace configured: npx shadcn@latest add https://ui.jengacore.com/r/<new-york-v4|base-nova>/button.json

Registry dependencies

npm packages

class-variance-authorityradix-ui@base-ui/react

Source

components/ui/button.tsx
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { LoaderCircle } from "lucide-react"
import { Slot } from "radix-ui"

import { cn } from "@/lib/utils"

/**
 * jengacore Button: shadcn API, jengacore look.
 * One `default` (primary) per view. `destructive` only behind a confirm dialog that repeats the object.
 * Labels are verbs in sentence case ("Approve batch"). Heights 30 / 34 / 44px; `lg` for teller and touch screens.
 */
const buttonVariants = cva(
  "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-[13.5px] leading-5 font-medium whitespace-nowrap select-none " +
    "transition-[background-color,border-color,color,transform,filter] duration-fast ease-jc active:translate-y-px " +
    "outline-none focus-ring " +
    "disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-100 aria-invalid:outline-destructive " +
    "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:stroke-[1.75] [&_svg:not([class*='size-'])]:size-4",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-forest-900 dark:hover:bg-[#6ee7b7] contrast:hover:bg-forest-900",
        secondary: "bg-secondary text-secondary-foreground hover:brightness-[.97] dark:hover:brightness-110",
        outline: "border border-border bg-card text-foreground hover:border-input hover:bg-accent contrast:border-input",
        ghost: "text-foreground/85 hover:bg-accent hover:text-foreground",
        destructive: "bg-destructive text-destructive-foreground hover:brightness-95 dark:hover:brightness-110",
        link: "h-auto px-0 text-primary underline-offset-4 hover:underline",
      },
      size: {
        default: "h-[34px] px-3.5 has-[>svg]:px-3",
        xs: "h-6 gap-1 rounded-sm px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
        sm: "h-[30px] gap-1.5 px-2.5 text-[12.5px] [&_svg:not([class*='size-'])]:size-3.5",
        lg: "h-11 px-5 text-[15px]",
        icon: "size-[34px]",
        "icon-xs": "size-6 rounded-sm [&_svg:not([class*='size-'])]:size-3",
        "icon-sm": "size-[30px]",
        "icon-lg": "size-11",
      },
    },
    defaultVariants: { variant: "default", size: "default" },
  }
)

function Button({
  className,
  variant = "default",
  size = "default",
  asChild = false,
  shortcut,
  loading = false,
  disabled,
  children,
  ...props
}: React.ComponentProps<"button"> &
  VariantProps<typeof buttonVariants> & {
    asChild?: boolean
    /** Keyboard hint shown after the label, e.g. "B" for Block. */
    shortcut?: string
    /** Working: disables the button and swaps the leading icon for a spinner; keep a verb label ("Posting…"). */
    loading?: boolean
  }) {
  const Comp = asChild ? Slot.Root : "button"
  return (
    <Comp
      data-slot="button"
      data-variant={variant}
      data-size={size}
      className={cn(buttonVariants({ variant, size, className }))}
      disabled={disabled || loading}
      aria-busy={loading || undefined}
      {...props}
    >
      {asChild ? (
        children
      ) : (
        <>
          {loading ? <LoaderCircle className="animate-spin" aria-hidden /> : null}
          {loading ? React.Children.toArray(children).filter((c) => !React.isValidElement(c) || c.type === React.Fragment || typeof c.type === "string") : children}
          {shortcut ? (
            <kbd className="rounded-[3px] border border-current px-1 font-mono text-[10.5px] leading-4 opacity-60">{shortcut}</kbd>
          ) : null}
        </>
      )}
    </Comp>
  )
}

export { Button, buttonVariants }