Skip to content

Button

Source: packages/ui/src/components/button.tsx Package: @brettjohnson/ui

Import

import { Button } from '@brettjohnson/ui';

Variants

Three visual variants cover all primary use cases:

VariantUsage
primaryPrimary CTA — one per section. “Book a Speaking Engagement”, “Submit Inquiry”.
secondarySecondary action alongside a primary. “Download Media Kit”.
ghostTertiary or navigation-adjacent actions. “Learn more”, “View all episodes”.
<Button variant="primary">Book a Speaking Engagement</Button>
<Button variant="secondary">Download Media Kit</Button>
<Button variant="ghost">Learn more</Button>

Primary

bg-brand-red text-white hover:bg-red-700

Used once per visual section. The red background must not compete with other red elements in the same viewport zone.

Secondary

border border-white/20 bg-transparent text-white hover:bg-white/10

Used when two actions of near-equal weight appear together (e.g., hero CTAs: “Book” + “Media Kit”).

Ghost

text-white/70 hover:text-white hover:bg-white/5

Used for inline or navigation-adjacent actions where a border or filled background would add visual noise.

Sizes

SizeHeightPaddingFont
sm36px (h-9)px-4text-sm
md44px (h-11)px-6text-base
lg56px (h-14)px-8text-lg
<Button size="sm">Small</Button>
<Button size="md">Medium (default)</Button>
<Button size="lg">Large</Button>

Default size is md. Use lg only in hero sections. Use sm in dense UI (table actions, inline forms).

Props

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'ghost';
size?: 'sm' | 'md' | 'lg';
className?: string;
}

The component extends all native <button> HTML attributes — onClick, disabled, type, aria-*, etc.

States

Disabled

<Button disabled>Unavailable</Button>

Disabled buttons receive opacity-50 and pointer-events-none — no separate disabled styling is required.

Loading

The Button component does not currently include a built-in loading state. Wrap with a loading indicator at the call site:

<Button disabled={isSubmitting} variant="primary">
{isSubmitting ? 'Submitting…' : 'Submit Inquiry'}
</Button>

Focus

All buttons use focus-visible:ring-2 focus-visible:ring-offset-2. Primary buttons ring in brand-red; secondary and ghost ring in white.

Usage Rules

  • One primary button per section. Multiple red buttons in the same view dilutes urgency.
  • Button labels are verbs. “Book”, “Download”, “Submit”, “Inquire” — not “Click Here” or “Learn More About Speaking”.
  • Never use <a> styled as a button for navigating between pages. Use Next.js <Link> for internal navigation and wrap it in the ghost variant if it visually appears as a button.

Full Example

import { Button } from '@brettjohnson/ui';
export function HeroCTA() {
return (
<div className="flex gap-4">
<Button variant="primary" size="lg">
Book a Speaking Engagement
</Button>
<Button variant="secondary" size="lg">
Download Media Kit
</Button>
</div>
);
}