<!-- Protocore Design System — DualCTA -->
# DualCTA

- **Category:** Cards (`cards`)
- **Slug:** `cards/dual-cta`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { DualCTA } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/cards/dual-cta

> The split page-closer — two hover-inverting CTA halves (mono eyebrow + headline + outbound arrow) in one bordered surface, divided by a hairline.

## When to use it

**DualCTA** is a page-level closer: the recurring "talk to us / start building" split that sits above the footer. It's a *layout* component for exactly two balanced choices, not a general button group. For a *toolbar* of several actions use **ButtonGroup**; for a *single* call to action a plain **Button** is right. For a *tinted note* with an action, use **Callout**. Reserve DualCTA for the two-way fork at the end of a page or section — one path per half, no more than two halves.

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `className` | `string` | no | — | — |
| `columns` | `DualCTAColumn[]` | yes | — | The CTA halves — typically two — laid into equal columns. |
| `style` | `CSSProperties` | no | — | — |

## Examples

### Basics

Pass two `columns`, each with an `eyebrow` and `title`. When a column has an `href`, the whole half renders as an `<a>` (with optional `target`/`rel`) and shows the outbound `↗︎`. Each half inverts on hover.

```tsx
import { DualCTA } from "@protocore/pds";

export default function DualCTABasics() {
  return (
    <DualCTA
      columns={[
        {
          eyebrow: "Talk to us",
          title: "Connect with our team",
          href: "#contact",
        },
        {
          eyebrow: "Self-serve",
          title: "Start building",
          href: "https://docs.example.com",
          target: "_blank",
          rel: "noreferrer",
        },
      ]}
    />
  );
}
```

### Button halves

Without an `href`, a half renders as a `<div>` and fires `onClick` — use it for in-app actions rather than navigation. Set `arrow={false}` to drop the outbound indicator when the action isn't a link.

```tsx
import { useState } from "react";
import { DualCTA } from "@protocore/pds";

export default function DualCTAActions() {
  const [choice, setChoice] = useState<string | null>(null);
  return (
    <div style={{ display: "grid", gap: 12 }}>
      <DualCTA
        columns={[
          {
            eyebrow: "Region",
            title: "Deploy to eu-central-1",
            arrow: false,
            onClick: () => setChoice("eu-central-1"),
          },
          {
            eyebrow: "Region",
            title: "Deploy to us-east-1",
            arrow: false,
            onClick: () => setChoice("us-east-1"),
          },
        ]}
      />
      <span style={{ fontSize: 13, color: "var(--pds-color-muted)" }}>
        {choice ? `Selected: ${choice}` : "Pick a region above."}
      </span>
    </div>
  );
}
```

## Usage

**Do**

- Give each half a parallel `eyebrow` + `title` so the two choices read as peers.
- Use `href` for navigation halves and `onClick` for in-app halves.
- Keep it to two columns — the layout is a balanced split, not an n-up grid.
- Drop the arrow (`arrow={false}`) on non-link halves so the `↗︎` doesn't imply navigation.

**Don't**

- Don't pass three or more columns; the halves stop being balanced.
- Don't mix a giant headline in one half with a terse one in the other — keep them symmetric.
- Don't use DualCTA as a generic button container; that's ButtonGroup.
- Don't set `target="_blank"` without a matching `rel` for external links.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Tab` | Moves focus to each CTA half in order. |
| `Enter` | Activates the focused half — follows the link, or fires onClick. |
| `Space` | Activates a link half (native anchor behavior). |

**Notes**

- Halves with an `href` are real anchors and are keyboard-focusable by default. Halves that only take `onClick` render as a `<div>` — add `role="button"` and `tabIndex={0}` via the column's props if the action must be keyboard-reachable, or prefer an `href` where one exists.
- The `↗︎` arrow is `aria-hidden`; the accessible name comes from the `title` (and `eyebrow`) text.
- The inversion on hover is mirrored by `:focus-visible`, so keyboard users see the same active-state feedback.

## Related

`card`, `callout`, `product-card`, `stat-strip`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
