Experimental: The AI extension and its component APIs may change in the future. Please give us feedback on your experience with them.
Import#
import { Confirmation } from '@dnb/eufemia/extensions/ai'import '@dnb/eufemia/extensions/ai/style'render(<Confirmationtitle="Continue with this action?"onConfirm={() => console.log('Confirmed')}onDecline={() => console.log('Declined')}>Review the details before deciding.</Confirmation>)
Description#
Ai.Confirmation presents a question, details and two decision buttons in a Card. Use it before proceeding with a proposed action, inside a conversation or on its own. For progress and optional Undo after an action starts, use Ai.AgentAction.
Your application controls status and supplies the decision callbacks. pending offers a choice, required highlights that a decision is needed, and confirmed, declined and expired keep the content visible with both buttons disabled. Only set confirmed or declined after your application has accepted that decision. Confirmation does not mean that the resulting action has completed. Missing handlers disable the corresponding button.
Set disabled while saving. Handle request failures in your application and show a useful message, for example with FormStatus, while keeping the decision available for retry when appropriate. The component does not run requests, expire automatically, or execute the action. Your application owns expiry, authorization and protection against repeated execution.
Accessibility#
The question and description label the card. Use level to fit its heading into the surrounding document. There is no modal dialog or focus trap. Buttons use type="button", so they do not submit surrounding forms.
State changes are announced politely with AriaLive; updates to the details are not announced automatically. Set announce={false} if your application already announces the outcome. The initial state is available through the card's description and is not announced automatically. When a state change disables the focused decision, focus moves to the card content; focus elsewhere is preserved. If your application removes the card, it must move focus to the appropriate next location. Verify the final flow with your supported screen readers.
Labels and translations#
Time limits belong to the application. Let users turn off, adjust or extend a limit before it expires. Give an accessible warning and at least 20 seconds to extend it, unless a WCAG timing exception applies.
confirmLabel and declineLabel override the translated Yes and No labels. Choose specific labels when they make the decision clearer, such as “Add to watchlist” and “Keep current list”. Your application translates the question and details. Override the extension's state labels through Provider translations. See AI translations and properties.
Demos#
Confirmation states#
The four design states: pending, required, confirmed and expired. The application owns their state.
Share history?
Share history?
RequiredShare history?
Share history?
Expired<Provider locale="en-GB"> <Flex.Vertical gap="medium" style={{ maxWidth: '32.6875rem', }} > {(['pending', 'required', 'confirmed', 'expired'] as const).map( (status) => ( <Confirmation key={status} title="Share history?" status={status} onConfirm={() => null} onDecline={() => null} > Do you want to give the customer advisor access to this conversation’s history? </Confirmation> ) )} </Flex.Vertical> </Provider>
Choose and reset#
Confirm or decline the request, then reset it to try again. A declined decision stays visible, with a text label rather than color alone.
Add these companies to your watchlist?
Påkrevdconst Decision = () => { const [status, setStatus] = useState< 'required' | 'confirmed' | 'declined' >('required') return ( <> <Confirmation title="Add these companies to your watchlist?" status={status} onConfirm={() => setStatus('confirmed')} onDecline={() => setStatus('declined')} confirmLabel="Add to watchlist" declineLabel="Keep current list" > Review the companies from the attached document before continuing. </Confirmation> <Button top onClick={() => setStatus('required')}> Reset decision </Button> </> ) } render(<Decision />)
Application-owned expiry#
Start a one-minute request, extend it or turn off the limit. The application warns before expiry and keeps completed decisions visible.
Continue with this request?
Utløptconst TimedDecision = () => { const [status, setStatus] = useState< 'expired' | 'required' | 'confirmed' | 'declined' >('expired') const startRef = useRef<HTMLButtonElement>(null) const [deadline, setDeadline] = useState<number>(null) const [now, setNow] = useState(Date.now()) useEffect(() => { if (status !== 'required' || deadline === null) { return undefined } const update = () => { const time = Date.now() setNow(time) if (time >= deadline) { const focused = document.activeElement if ( focused instanceof HTMLElement && focused.closest('[data-visual-test="ai-confirmation-expiry"]') && !focused.closest('.dnb-ai-confirmation') ) { startRef.current?.focus() } setStatus('expired') } } const interval = setInterval(update, 1000) update() return () => clearInterval(interval) }, [status, deadline]) return ( <> <Flex.Horizontal bottom> <Button ref={startRef} onClick={() => { setNow(Date.now()) setDeadline(Date.now() + 60000) setStatus('required') }} > {status === 'required' ? 'Restart request' : 'Start a request'} </Button> {status === 'required' && deadline !== null && ( <> <Button onClick={() => setDeadline(Date.now() + 60000)}> Extend by one minute </Button> <Button onClick={() => { setDeadline(null) startRef.current?.focus() }} > Turn off time limit </Button> </> )} </Flex.Horizontal> <Confirmation title="Continue with this request?" status={status} onConfirm={() => setStatus('confirmed')} onDecline={() => setStatus('declined')} > {status === 'required' && deadline !== null ? 'You have ' + Math.max(0, Math.ceil((deadline - now) / 1000)) + ' seconds. Extend or turn off the limit if you need more time.' : 'Review the request before continuing.'} </Confirmation> <AriaLive> {status === 'required' && deadline !== null && deadline - now <= 30000 ? 'This request expires soon. Extend or turn off the time limit using the buttons above.' : ''} </AriaLive> </> ) } render(<TimedDecision />)