# Button Component

A polymorphic button component that can render as either a native `<button>` element for interactive actions or an `<a>` element for navigation, with full TypeScript type safety through discriminated unions.

## Import path
```tsx
import Button from '@hubspot/cms-component-library/Button';
```

## Purpose

The Button component provides a unified interface for creating interactive buttons and styled links in HubSpot CMS projects. It solves the common challenge of maintaining visual consistency between clickable elements that perform different functions (navigation vs. actions). `buttonType="link"` should be used most of the time and is default. `buttonType="button"` should only be used when an onClick handler is needed.

## Component Structure

```
Button/
├── index.tsx                     # Main component with render logic
├── types.ts                      # TypeScript type definitions (discriminated unions)
├── ContentFields.tsx             # HubSpot field definitions for content
├── StyleFields.tsx               # HubSpot field definitions for styling
├── index.module.scss             # CSS module with design tokens
└── stories/
    ├── Button.AsButton.stories.tsx  # Interactive button examples
    ├── Button.AsLink.stories.tsx    # Link navigation examples
    ├── ButtonArgs.ts                 # Storybook argument types
    ├── ButtonDecorator.tsx           # Storybook decorator
    └── ButtonDecorator.module.css    # Decorator styles
```

## Components

### Button (Main Component)

**Purpose:** Polymorphic component that renders either a `<button>` or `<a>` element based on the `buttonType` prop.

**Base Props (shared by both types):**
```tsx
{
  variant?: 'primaryButton' | 'secondaryButton' | 'tertiaryButton' | 'accentButton';  // Visual style variant (connected to theme settings)
  className?: string;                               // Additional CSS classes
  style?: React.CSSProperties;                      // Inline styles
  children?: React.ReactNode;                       // Button text/content
  iconFieldPath?: string;                           // Path to icon in HubSpot fields
  iconPosition?: 'left' | 'right';                  // Icon placement
  showIcon?: boolean;                               // Toggle icon visibility
  iconPurpose?: 'SEMANTIC' | 'DECORATIVE';         // Icon accessibility role
  iconTitle?: string;                               // Icon title for screen readers
}
```

**Button Type (`buttonType="button"`) - Additional Props:**
```tsx
{
  buttonType: 'button';           // Renders as <button> element
  onClick?: () => void;           // Click handler (requires JS islands)
  disabled?: boolean;             // Disables button interaction
}
```

**Link Type (`buttonType="link"`) - Additional Props:**
```tsx
{
  buttonType: 'link';                              // Renders as <a> element
  href?: string;                                   // Link destination URL
  target?: '_self' | '_blank' | '_parent' | '_top'; // Link target behavior
  rel?: string;                                    // Link relationship (e.g., 'noopener noreferrer')
}
```

## Usage Examples

### Basic Interactive Button

Custom Component wrapper
```tsx
import Button from '@hubspot/cms-component-library/Button';

export default function CustomButtonIsland({buttonType, ...rest}){

  // AI / User can now define interactive state or click handlers
  const onClickHandler = () => {
    alert('Button Clicked');
  }

  return <Button buttonType="button" {...rest} onClick={onClickHandler} />
}
```

Module file
```tsx
import CustomButtonIsland from './path/to/created/CustomButtonIsland?island';
import { Island } from '@hubspot/cms-components'

...

<Island module={CustomButtonIsland} {...props}>

...

```



### Basic Link Button

```tsx
<Button
  buttonType="link"
  variant="primaryButton"
  href="https://www.hubspot.com"
  target="_self"
>
  Visit HubSpot
</Button>
```

### External Link (New Tab)

```tsx
<Button
  buttonType="link"
  variant="primaryButton"
  href="https://www.hubspot.com"
  target="_blank"
  rel="noopener noreferrer"
>
  Open in New Tab
</Button>
```

### Button with Icon (Right Position)

```tsx
<Button
  buttonType="button"
  variant="primaryButton"
  onClick={handleClick}
  showIcon={true}
  iconFieldPath="icon"
  iconPosition="right"
  iconPurpose="DECORATIVE"
>
  Next Step
</Button>
```

### Button with Icon (Left Position)

```tsx
<Button
  buttonType="link"
  variant="secondaryButton"
  href="/back"
  showIcon={true}
  iconFieldPath="icon"
  iconPosition="left"
  iconPurpose="SEMANTIC"
  iconTitle="Go back"
>
  Back
</Button>
```

### Disabled Button

```tsx
<Button
  buttonType="button"
  variant="primaryButton"
  disabled={true}
  onClick={handleClick}
>
  Submit (disabled)
</Button>
```

### With Custom Styling

```tsx
<Button
  buttonType="link"
  variant="primaryButton"
  href="/custom"
  className="custom-button-class"
  style={{ maxWidth: '300px' }}
>
  Custom Styled Button
</Button>
```

## HubSpot CMS Integration

### Field Definitions

The Button component provides field definitions for easy integration with HubSpot CMS modules.

#### ContentFields.tsx

Configurable props for customizing field labels, names, and defaults:

```tsx
<Button.ContentFields
  buttonTextLabel="Button text"
  buttonTextName="buttonText"
  buttonTextDefault="Button"
  buttonLinkLabel="Button link"
  buttonLinkName="buttonLink"
  buttonLinkDefault={{
    url: {
      type: 'EXTERNAL',
      content_id: null,
      href: 'https://www.hubspot.com',
    },
  }}
  showButtonLink={true}
  iconLabel="Button icon"
  iconName="icon"
  iconDefault={/* icon default */}
  showIconLabel="Show button icon"
  showIconName="showIcon"
  showIconDefault={false}
  buttonIconPositionLabel="Icon position"
  buttonIconPositionName="buttonIconPosition"
  buttonIconPositionDefault="right"
/>
```

**Props:**
- `buttonTextLabel?: string` — label for the TextField (default: `'Button text'`)
- `buttonTextName?: TButtonTextName` — field name (default: `'buttonText'`). Generic `TButtonTextName extends string` lets consumers narrow the field-name literal for compile-time key matching on `fieldVisibility`.
- `buttonTextDefault?` — default value for the TextField
- `buttonLinkLabel?: string` — label for the LinkField (default: `'Button link'`)
- `buttonLinkName?: TButtonLinkName` — field name (default: `'buttonLink'`). Generic `TButtonLinkName extends string` lets consumers narrow the field-name literal for compile-time key matching on `fieldVisibility`.
- `buttonLinkDefault?` — default value for the LinkField
- `showButtonLink?: boolean` — hide the LinkField when `false` (default: `true`)
- `fieldVisibility?: Partial<Record<TButtonTextName | TButtonLinkName, Visibility>>` — visibility options keyed by field name. Keys must match the `buttonTextName` or `buttonLinkName` props at compile time. Values match the cms-components Visibility schema (e.g. `hidden_subfields`, `controlling_field_path`, `operator`).

**Example — hide the nofollow subfield on the default `buttonLink`:**
```tsx
<Button.ContentFields
  fieldVisibility={{ buttonLink: { hidden_subfields: { no_follow: true } } }}
/>
```

**Example — custom field names (keys must match):**
```tsx
<Button.ContentFields
  buttonTextName="ctaText"
  buttonLinkName="ctaLink"
  fieldVisibility={{ ctaLink: { hidden_subfields: { no_follow: true } } }}
/>
```

**Fields:**
- `buttonText`: TextField for button text content
- `buttonLink`: LinkField for href destination (can be hidden with `showButtonLink={false}`)
- `icon`: Icon field using Icon.ContentFields
- `showIcon`: Toggle for icon visibility
- `buttonIconPosition`: ChoiceField for selecting icon placement (left, right). This field has a hardcoded visibility rule — it is only shown when the icon toggle is enabled (`iconComponentShowIcon === true`), which corresponds to the `BooleanField` id hardcoded in `Icon.ContentFields`.

#### StyleFields.tsx

Configurable props for variant:

```tsx
<Button.StyleFields
  buttonVariantLabel="Button style"
  buttonVariantName="buttonVariant"
  buttonVariantDefault={{ variant_name: 'primaryButton' }}
/>
```

**Fields:**
- `buttonVariant`: VariantSelectionField connected to theme settings (primaryButton, secondaryButton, tertiaryButton, accentButton)

### Module Usage Example

```tsx
import Button from '@hubspot/cms-component-library/Button';

export default function CTAModule({ fieldValues }) {
  return (
    <Button
      buttonType="link"
      variant={fieldValues.buttonVariant}
      href={fieldValues.buttonLink?.url?.href}
      target="_blank"
      rel="noopener noreferrer"
      showIcon={fieldValues.showIcon}
      iconFieldPath="icon"
      iconPosition="right"
    >
      {fieldValues.buttonText}
    </Button>
  );
}
```

## Styling

### Theme CSS Variables

The Button component is themed via `--hs-button-*` CSS variables, which are provided by the theme settings variant system (scoped via the variant prop and `data-buttons-variant` attribute).

**Base Styles (from theme):**
- `--hs-button-backgroundColor`: Background color
- `--hs-button-color`: Text color
- `--hs-button-borderRadius`: Border radius
- `--hs-button-borderStyle`: Border style
- `--hs-button-borderWidth`: Border width
- `--hs-button-borderColor`: Border color
- `--hs-button-fontSize`: Font size
- `--hs-button-fontFamily`: Font family
- `--hs-button-fontStyle`: Font style
- `--hs-button-fontWeight`: Font weight
- `--hs-button-textDecoration`: Text decoration

**Hover States (from theme):**
- `--hs-button-backgroundColor-hover`: Hover background (falls back to base)
- `--hs-button-color-hover`: Hover text color (falls back to base)
- `--hs-button-borderStyle-hover`: Hover border style (falls back to base)
- `--hs-button-borderWidth-hover`: Hover border width (falls back to base)
- `--hs-button-borderColor-hover`: Hover border color (falls back to base)

**Focus States (from theme):**
- `--hs-button-backgroundColor-focus`: Focus background (falls back to base)
- `--hs-button-color-focus`: Focus text color (falls back to base)
- `--hs-button-borderStyle-focus`: Focus border style (falls back to base)
- `--hs-button-borderWidth-focus`: Focus border width (falls back to base)
- `--hs-button-borderColor-focus`: Focus border color (falls back to base)

**Active States (from theme):**
- `--hs-button-backgroundColor-active`: Active background (falls back to base)
- `--hs-button-color-active`: Active text color (falls back to base)
- `--hs-button-borderStyle-active`: Active border style (falls back to base)
- `--hs-button-borderWidth-active`: Active border width (falls back to base)
- `--hs-button-borderColor-active`: Active border color (falls back to base)

### Hardcoded Layout Values

The following properties use fixed values and are not configurable via CSS variables:

- **Gap:** 8px (space between text and icon)
- **Padding:** 12px vertical, 24px horizontal
- **Icon fill:** currentColor

The component itself does not declare a focus outline — the keyboard focus indicator is provided globally by the consuming theme's stylesheet (e.g. the focus-visible rule in `web-default-templates`'s `main.css`).

## Accessibility

The Button component follows accessibility best practices:

- **Semantic HTML**: Renders appropriate elements (`<button>` or `<a>`) based on context
- **Keyboard Navigation**: Native keyboard support for both button and link variants
  - Buttons: Space and Enter keys activate
  - Links: Enter key navigates
- **Focus Management**: CSS-based focus styles with outline for keyboard users
- **Disabled State**:
  - Only available for `buttonType="button"`
  - Sets `disabled` attribute and reduces opacity
  - Prevents click events and shows not-allowed cursor
- **Icon Accessibility**:
  - `iconPurpose="SEMANTIC"`: Icon conveys meaning (includes accessible title)
  - `iconPurpose="DECORATIVE"`: Icon is visual only (aria-hidden)
  - `iconTitle` provides screen reader description for semantic icons
- **Link Security**:
  - Use `rel="noopener noreferrer"` with `target="_blank"` to prevent security vulnerabilities


## Best Practices

- **Choose the right type**: Use `buttonType="button"` for actions (submit, toggle, open modal) and `buttonType="link"` for navigation
- **Never use `<a>` for actions**: If it's not navigating to a URL, use `buttonType="button"`
- **Always use `rel="noopener noreferrer"` with `target="_blank"`**: Prevents security vulnerabilities with external links
- **Button type requires JavaScript islands**: Interactive buttons with `onClick` handlers only work in JavaScript islands, not in static HubSpot modules
- **Icon positioning**: Place icons at the end (right) for forward actions, at the start (left) for back/previous actions
- **Icon purpose**: Use `SEMANTIC` when the icon adds meaning, `DECORATIVE` when it's purely visual
- **Disabled state**: Only use with `buttonType="button"`, not applicable to links
- **CSS Variables**: Override design tokens using CSS variables rather than hardcoding values
- **Variant system**: Variants are connected to theme settings via `VariantSelectionField` and `data-variant-name` attribute. CSS variables are scoped automatically by the ContentUILib variant system.

## Common Patterns

### Call-to-Action (CTA) Button

```tsx
<Button
  buttonType="link"
  variant="primaryButton"
  href="/signup"
  showIcon={true}
  iconFieldPath="icon"
  iconPosition="right"
>
  Get Started
</Button>
```

### Form Submit Button

```tsx
<Button
  buttonType="button"
  variant="primaryButton"
  onClick={handleSubmit}
  disabled={isSubmitting}
>
  {isSubmitting ? 'Submitting...' : 'Submit Form'}
</Button>
```

### Navigation Button with Icon

```tsx
<Button
  buttonType="link"
  variant="secondaryButton"
  href="/dashboard"
  showIcon={true}
  iconFieldPath="icon"
  iconPosition="left"
>
  Back to Dashboard
</Button>
```

### External Resource Link

```tsx
<Button
  buttonType="link"
  variant="tertiaryButton"
  href="https://docs.hubspot.com"
  target="_blank"
  rel="noopener noreferrer"
  showIcon={true}
  iconFieldPath="icon"
  iconPosition="right"
>
  View Documentation
</Button>
```

## Related Components

- **Icon**: Used internally for icon rendering. Can be used standalone for custom icon implementations.

