Experimental: The AI extension and its component APIs may change in the future. Please give us feedback on your experience with them.
Import#
import { AgentAction } from '@dnb/eufemia/extensions/ai'import '@dnb/eufemia/extensions/ai/style'render(<AgentAction>Generating a graph…</AgentAction>)
Description#
Ai.AgentAction shows a compact action row with a decorative agent avatar, an animated progress pill, and a result. It can also show Undo with a countdown. The design follows Agent Action in Figma.
Use it for actions such as paying a bill or generating a graph when you need progress, a short text result and optional Undo. Ai.Task also shows an operation and its outcome, but accepts details, decision controls and structured results below a status row. Choose by the interaction you need, rather than whether the operation comes from an agent or a framework tool. See Choosing Task or AgentAction.
Controlling an action#
The application owns execution, authorization, progress, and the result. Map your framework's state to running, done, error, or canceled, and supply text describing the current state. The component does not start or complete an action.
While running, actionLabel is an optional verb before the pill and children is the text inside it. For example, use actionLabel="Paying" with children="Spotify Premium". When the state changes, replace children with the result, such as Spotify Premium paid.. The verb and pill disappear.
Pass progress from 0 to 100 when the application knows the actual progress. Omit it when the progress is unknown. A value of 100 still shows a running action until the application confirms status="done". Do not infer success from elapsed time or an animation ending. Use a separate component instance or React key for each action.
Offering Undo#
Provide onUndo only when the application can reverse a completed action. It is shown only for status="done". The callback receives the Eufemia Button click event; the application performs the reversal and updates the text and state once the outcome is known. Set undoDisabled while a reversal is pending, and present any failure in your application.
For a time-limited reversal, pass undoUntil as an absolute Unix timestamp in milliseconds, preferably from the action result. The countdown uses the clock, so re-rendering or mounting an existing action does not restart the time window. Undo disappears at the deadline, and a late click is ignored. The application must still enforce the deadline when handling the request. Without undoUntil, Undo stays available until the application removes onUndo or changes the state.
Accessibility and motion#
The component announces status and text changes through Eufemia AriaLive, including changes to children while the status stays the same. Announcements are polite and atomic, with a one-second debounce. Keep the component mounted and update its props. Use short, complete status messages rather than streaming text into the action label.
When Undo becomes available, the announcement includes the button label and any remaining time limit. The button description gives a stable, localized expiry time. Expiry is announced once; countdown ticks and progress updates do not trigger live announcements. A progressbar exposes its name and percentage when known, or an indeterminate state when progress is unknown.
Set announce={false} for historical actions or when your application owns announcements. This silences initial and subsequent announcements while preserving the content, progress semantics, and button description. Avoid wrapping AgentAction in another live region while its own announcements are enabled.
The Undo control uses Eufemia Button and returns focus to the action status if it becomes disabled or expires while focused. It preserves focus elsewhere. In forced-colors mode, the fill has a boundary using the system text color. Reduced motion disables the animation while preserving the state and percentage. The running text keeps its normal color.
Your application supplies meaningful, translated action and result text, changes the status when execution or reversal finishes, and presents any errors. It also owns confirmation, authorization, and enforcement of the Undo deadline. Assess whether a time limit is appropriate and provide a longer window or another reversal mechanism when needed. The component does not announce changes to other content elsewhere in your application.
The agentAvatar identifies the agent to the left of the action row. It defaults to Aino; set agentAvatar={false} to hide it. The targetVisual appears beside the target text inside the progress pill and accepts an Avatar, icon, or image. A small Avatar with hasLabel works in either position. Both visuals are decorative; use the text to identify the action and result.
Demos#
Agent action states#
<Provider locale="en-GB"> <Flex.Stack> <AgentAction actionLabel="Paying" announce={false} progress={35} targetVisual={ <Avatar size="small" src={spotifyAvatar} alt="" hasLabel /> } > Spotify Premium </AgentAction> <AgentAction actionLabel="Paying" announce={false} progress={65} agentAvatar={ <Avatar size="small" icon={solve} variant="tertiary" hasLabel /> } targetVisual={ <Avatar size="small" src={spotifyAvatar} alt="" hasLabel /> } > Spotify Premium </AgentAction> <AgentAction announce={false}>Generating a graph…</AgentAction> <AgentAction status="done" announce={false}> Spotify Premium paid. </AgentAction> <AgentAction status="error" announce={false}> Payment failed. Please try again. </AgentAction> <AgentAction status="canceled" announce={false}> Graph generation canceled. </AgentAction> </Flex.Stack> </Provider>
Known progress and timed Undo#
This example simulates a confirmed payment and a reversible result. Its timers belong to the demo application. Production progress and completion must come from the actual action.
const PaymentDemo = () => { const [progress, setProgress] = useState(0) const [status, setStatus] = useState<AiAgentActionStatus>('running') const [undoUntil, setUndoUntil] = useState<number>() useEffect(() => { if (status !== 'running') { return undefined } const started = Date.now() const interval = setInterval(() => { const value = Math.min(100, (Date.now() - started) / 22) setProgress(value) if (value === 100) { // Simulate a confirmed result with a 20-second reversal window. clearInterval(interval) setUndoUntil(Date.now() + 20000) setStatus('done') } }, 110) return () => clearInterval(interval) }, [status]) return ( <Provider locale="en-GB"> <Flex.Stack> <AgentAction actionLabel="Paying" progress={progress} status={status} targetVisual={ <Avatar size="small" src={spotifyAvatar} alt="" hasLabel /> } undoUntil={undoUntil} onUndo={() => setStatus('canceled')} > {status === 'running' ? 'Spotify Premium' : status === 'done' ? 'Spotify Premium paid.' : 'Payment reversed.'} </AgentAction> <Button variant="secondary" disabled={status === 'running'} onClick={() => { setProgress(0) setUndoUntil(undefined) setStatus('running') }} > Replay payment </Button> </Flex.Stack> </Provider> ) } render(<PaymentDemo />)
Unknown progress and cancellation#
Use indeterminate progress until the application receives a result. The controls here simulate completion and cancellation.
const GraphDemo = () => { const [status, setStatus] = useState<AiAgentActionStatus>('running') return ( <Provider locale="en-GB"> <Flex.Stack> <AgentAction status={status}> {status === 'running' ? 'Generating a graph…' : status === 'done' ? 'Graph generated.' : 'Graph generation canceled.'} </AgentAction> <Flex.Horizontal> <Button disabled={status !== 'running'} onClick={() => setStatus('done')} > Complete graph </Button> <Button variant="secondary" disabled={status !== 'running'} onClick={() => setStatus('canceled')} > Cancel graph </Button> <Button variant="tertiary" icon={false} disabled={status === 'running'} onClick={() => setStatus('running')} > Restart graph </Button> </Flex.Horizontal> </Flex.Stack> </Provider> ) } render(<GraphDemo />)
Pending Undo#
Disable Undo while the application handles a reversal. Confirm the outcome before updating the result. This demo lets you simulate that response or add a deadline to an existing Undo control.
const UndoDemo = () => { const [pending, setPending] = useState(false) const [reversed, setReversed] = useState(false) const [undoUntil, setUndoUntil] = useState<number>() return ( <Provider locale="en-GB"> <Flex.Stack> <AgentAction status={reversed ? 'canceled' : 'done'} undoDisabled={pending} undoUntil={undoUntil} onUndo={() => setPending(true)} > {reversed ? 'Payment reversed.' : pending ? 'Reversing payment…' : 'Spotify Premium paid.'} </AgentAction> <Flex.Horizontal> <Button disabled={!pending} onClick={() => { setPending(false) setReversed(true) }} > Confirm reversal </Button> <Button onClick={() => setUndoUntil(Date.now() + 20000)}> Add Undo deadline </Button> <Button onClick={() => { setPending(false) setReversed(false) setUndoUntil(undefined) }} > Reset payment </Button> </Flex.Horizontal> </Flex.Stack> </Provider> ) } render(<UndoDemo />)
Historical actions#
Use announce={false} when rendering saved conversation history. The content stays available to screen-reader navigation without announcing each past action on mount.
<Provider locale="en-GB"> <Flex.Stack> <AgentAction status="done" announce={false}> Earlier payment completed. </AgentAction> <AgentAction status="canceled" announce={false}> Earlier graph generation canceled. </AgentAction> </Flex.Stack> </Provider>