← All proposals

Agentic Button

A separate component, `AgenticButton`, for an action the agent does:

Net-new componentOpen in Storybook

proposals/AgenticButton/HANDOFF.md65 lines · View on GitHub

Handoff: Agentic Button

What and why

A button for an action the agent does: the secondary Button look with the AI gradient as its border, and the gradient working spinner. It is a separate component, not an option of the Button (decided 2026-10-21; it was an isAgentic prop, then a variant attempt before that). It takes every Button prop except variant, because there is one look: the secondary treatment, not the primary one. It reuses the Button's markup, classes (pds-button, pds-button--secondary, the sizes) and CSS file, and adds the class pds-agentic-button.

  • Figma / Jira: none. Source: Erika's design for an agentic button. The Figma page of the Button is out of date (it still shows the old agentic option).
  • Context: context.md

Exploration and decisions

  • Separate component, secondary only. The agentic button is its own component; the Button goes back to plain PDS. No primary (dark) version.
  • Border technique. --pds-gradient-ai-active is a gradient, so it cannot go in border-color. The border is transparent, the gradient paints the border-box, and the secondary fill sits on the padding-box over an opaque --pds-color-surface-default layer so the translucent secondary fill does not show the gradient through.
  • Working spinner. isWorking always shows the gradient Spinner; isLoading clears the gradient so the pulse shows cleanly.
  • isAnimated shimmers the border (the animated agentic Panel's loop; off for reduced motion). Opt-in; the stories turn it on.

Full list: decisions.md.

API

Every Button prop except variant (see the Button handoff), plus:

Prop Type Default Purpose
isAnimated boolean false Shimmers the gradient border. Off for reduced motion.

Variants and states: all four sizes; default, disabled, loading, working (with disableWhenWorking), with icon, tally and status dot; the gradient border. Stories: Default, With Icon, With Tally, With Status Dot, Loading State, Working State, Working State Interactive, With Offset, All States, All Sizes.

Tokens used

  • --pds-animation-duration-ai-shimmer
  • --pds-color-surface-default
  • --pds-gradient-ai-active

The rest is the Button's own (it reuses its CSS).

Impact

From impact.md: no existing PDS component is reached; everything is scoped to .pds-agentic-button. Proposals that build on it: AgenticMenuButton (its trigger) and Panel (the Approval Report's Approve button), and through them LabeledDivider. The Section Message used it briefly and now uses the standard secondary Button.

Reused vs net-new

  • Reused: the whole Button (markup, classes, CSS), Icon, Tally, Tooltip, the local Spinner (its agentic color).
  • Net-new: .pds-agentic-button with --animated, and the prop isAnimated. See custom.md (no raw values).

Accessibility

  • Unchanged from the Button: a native button, visible focus ring, ariaLabel, and the loading and working states.
  • The gradient border is decorative; the label and the spinner's label carry the meaning.
  • The a11y panel has not been run on these stories, and light and dark were not checked by eye in a session. Run it before the port.

Decisions and open questions

  • Should there be a filled (primary) agentic button later, for the one main action on a screen?
  • It repeats the Button's component logic (about 300 lines). The port may prefer to let the Button take the agentic styling as an internal detail; that would reintroduce a prop on Button.

Port notes

  • New component: use npm run generate:component, then copy the files. It reuses the PDS Button's markup and CSS (the local base copy is in proposals/_base/Button/, see its README). The only change over the published Button is a display: contents span with the class pds-button__leading around the leading spinner, icon and status dot (it keeps page translation from breaking React); add it to the Button when porting. It also needs Spinner (the agentic color).
  • Swap the local utils stand-ins (mergeClasses, StatusDot, extractText) for the real @libs/... imports.

Preview

npm run storybook, then Component Proposals/Buttons/Agentic Button. Figma: none yet.