---
title: 'Ai.Confirmation'
description: 'Asks the user to confirm or decline a proposed action.'
version: 11.17.0
generatedAt: 2026-11-05T09:55:47.089Z
checksum: d5009ea4580832926e9d9c23ffd428766c64bded6a7b64b89c1152d1f57d7c66
---

# Ai.Confirmation

**Experimental:** The AI extension and its component APIs may change in the future. Please give us feedback on your experience with them.

## Import

```tsx
import { Confirmation } from '@dnb/eufemia/extensions/ai'
import '@dnb/eufemia/extensions/ai/style'

render(
  <Confirmation
    title="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](/web/components/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](/web/patterns-and-extensions/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](/web/components/form-status/), 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](/web/components/aria-live/); 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](/web/patterns-and-extensions/ai/translations/) and [properties](/web/patterns-and-extensions/ai/Confirmation/properties/#translations).


## Demos

### Confirmation states

The four design states: pending, required, confirmed and expired. The application owns their state.


```tsx
render(<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.


```tsx
const 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.


```tsx
const 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 />);
```

<!-- eufemia-properties-usage:start -->
Some properties provide flexibility for specific integrations. Follow the component's usage guidance. Customizations beyond the documented patterns need a use case approved by DNB UX. [Read about intended use and customization](/web/styling-and-themes/configuration/component-properties/#intended-use-and-customization).
<!-- eufemia-properties-usage:end -->

## Properties


```json
{
  "props": {
    "title": {
      "doc": "The question heading.",
      "type": "string",
      "status": "required"
    },
    "children": {
      "doc": "Explanation or details of the decision.",
      "type": "React.ReactNode",
      "status": "required"
    },
    "status": {
      "doc": "Controlled decision state. Completed or expired decisions disable both buttons.",
      "type": [
        "\"pending\"",
        "\"required\"",
        "\"confirmed\"",
        "\"declined\"",
        "\"expired\""
      ],
      "defaultValue": "'pending'",
      "status": "optional"
    },
    "confirmLabel": {
      "doc": "Confirm button text. Defaults to a translated Yes.",
      "type": "string",
      "status": "optional"
    },
    "declineLabel": {
      "doc": "Decline button text. Defaults to a translated No.",
      "type": "string",
      "status": "optional"
    },
    "disabled": {
      "doc": "Disable both buttons, for example while saving a decision.",
      "type": "boolean",
      "defaultValue": "false",
      "status": "optional"
    },
    "announce": {
      "doc": "Announce decision-state changes politely. Disable when the application already announces the outcome.",
      "type": "boolean",
      "defaultValue": "true",
      "status": "optional"
    },
    "level": {
      "doc": "Heading level.",
      "type": "number",
      "defaultValue": "3",
      "status": "optional"
    },
    "[Space](/uilib/layout/space/properties)": {
      "doc": "Spacing properties like `top` or `bottom` are supported.",
      "type": [
        "string",
        "object"
      ],
      "status": "optional"
    }
  },
  "usageGuidance": "Some properties provide flexibility for specific integrations. Follow the component's usage guidance. Customizations beyond the documented patterns need a use case approved by DNB UX. [Read about intended use and customization](/web/styling-and-themes/configuration/component-properties/#intended-use-and-customization)."
}
```


## Translations

| Key                     | Purpose                                  |
| ----------------------- | ---------------------------------------- |
| `confirmationYes`       | Default confirm button label.            |
| `confirmationNo`        | Default decline button label.            |
| `confirmationPending`   | Accessible label for a pending decision. |
| `confirmationRequired`  | Required status label.                   |
| `confirmationConfirmed` | Confirmed status label.                  |
| `confirmationDeclined`  | Declined status label.                   |
| `confirmationExpired`   | Expired status label.                    |

## Events


```json
{
  "props": {
    "onConfirm": {
      "doc": "Called when the user confirms. Receives the Button click event. The application owns the decision and resulting action. The button is disabled when no handler is provided.",
      "type": "function",
      "status": "optional"
    },
    "onDecline": {
      "doc": "Called when the user declines. Receives the Button click event. The application owns the decision and resulting action. The button is disabled when no handler is provided.",
      "type": "function",
      "status": "optional"
    }
  }
}
```
