Handoff: IconButton (agentic and primary variants, working)
What and why
The agentic treatment for icon-only buttons, matching the Agentic Button. This proposal is the v2 IconButton plus an agentic variant (the AI gradient as the border; it replaced an isAgentic option on 2026-10-21, to be an extension like Send), a primary variant (filled like a primary Button) and isWorking (a spinner). The last two replace the separate SendButton proposal (2026-10-21): Send is a primary icon button. Everything else is unchanged, so the port is a diff against src/components/buttons/IconButton on v2.x.
- Figma: PDS Design Library branch
skill-test(awaiting review): Icon Button set gainsAgentic=No/Yes. Jira: none. - Context:
context.md
Exploration and decisions
variant='agentic'(2026-10-21). An extension of the Icon Button, like Send: the AI gradient (the same token as the Agentic Button) as the border, always bordered, sohasBorderdoes not apply. It replaced anisAgenticboolean.- Send is a primary icon button (2026-10-21). The
SendButtonproposal was removed; its three states are made from props:iconName='paperPlane'(send),isWorking(a spinner; disabled;aria-busy) andiconName='stop'(stop, a new filled-square icon in the Icon wrapper).primaryis the primary Button's fill and hover and active colors, one 40% fade when disabled (or working), a focus ring offset by 1px.isWorkingtakes no color option: the spinner iscurrentColor(SendButton'sspinnerColorwas dropped; nothing used it). - Technique: a transparent border with the gradient on the border-box and the fill on the padding-box, over an opaque surface layer. The fill is a custom property (
--icon-button-fill) so hover, active and pressed can swap it. Pressed uses the secondary active background instead of v2's border-color fill, which a gradient cannot provide.
API
Two additions. All other props are unchanged from v2.
| Prop | Type | Default | Purpose |
|---|---|---|---|
isWorking |
boolean |
false |
A spinner replaces the icon; the button is disabled and has aria-busy. |
variant |
adds 'primary' |
'standard' |
agentic is the AI gradient border; primary is filled like a primary Button, for the main action of a toolbar (Send). |
Variants and states: every v2 variant, size and state is unchanged. agentic adds the gradient border (rest, hover, active, pressed) at xs, s and m. primary and isWorking add the filled Send button at the same sizes. Stories: Default (agentic), Sizes, With Border, Pressed, Primary All States, Primary Send.
Tokens used
New to this proposal: --pds-gradient-ai-active. The rest is the v2 IconButton's:
--pds-animation-transition-button--pds-border-radius-input--pds-border-width-default--pds-border-width-outline--pds-color-button-primary-background-default,-hover,-active,--pds-color-button-primary-foreground,--pds-elevation-button-primary-active--pds-color-button-critical-secondary-*,--pds-color-button-reverse-secondary-*,--pds-color-button-secondary-*(background, border and foreground states)--pds-color-interactive-focus,--pds-color-interactive-reverse-focus--pds-color-surface-default--pds-elevation-button-critical-secondary-active,-reverse-secondary-active,-secondary-active,--pds-elevation-icon-button-pressed--pds-spacing-button-height-m,-s,-xs
Impact
From impact.md: no existing PDS component is reached. The additions are the .pds-icon-button--agentic (variant) rules (rest, hover, active and pressed), which only apply where that class is added. The proposals that build on it are AgenticMenuButton (its icon-only trigger is variant='agentic') and, through it, LabeledDivider (ready for dev). It is also composed by ChatInput (ready for dev). Port IconButton before them (see docs/PORT-ORDER.md). It is also the Send button of ChatInput and the Chat with an agent recipe (variant='primary').
Reused vs net-new
- Reused: the whole v2
IconButton;IconandTooltip. New dependency: the localSpinner(forisWorking) and thestopicon in the localIconwrapper. - Net-new: the
isWorkingprop, theagenticandprimaryvariants,.pds-icon-button--agentic,--primaryand--working, with seven--icon-button-*variables (seecustom.md; no raw values).
Accessibility
- Unchanged from v2: native
button, requiredariaLabel, tooltip, pressed state, visible focus ring. - The gradient border is decorative.
- The a11y panel has not been run on these stories. Run it in light and dark before the port.
Decisions and open questions
- Should
criticalorreverseget the border too? primaryhas no border option and no agentic treatment. Does Send need the gradient border? Is a waveform (voice) state wanted when the field is empty?- The
stopicon is drawn in the Icon wrapper (Font Awesome Free solidstop); the port adds it as a registry entry. - Strict-null fixes in the local copy (for example
icon2Namedefaults toundefined, notnull) change no behavior. v2 is not strict, this repo is.
Port notes
- Edit
src/components/buttons/IconButtonin place. Do not use the generator.isWorkingmakes IconButton depend on Spinner, so port Spinner first (seedocs/PORT-ORDER.md). - Swap the local
mergeClassesfor the real one.
Preview
npm run storybook, then Component Proposals/Buttons/Icon Button.
Figma: https://www.figma.com/design/AMGWJbraJoi2PKWnxo29Ah/PDS-Proposals?node-id=374-48, page Icon Button (4 stories, light only; captures of Storybook with library icons swapped in, sent 2026-10-20).