---
title: 'Ai.Response'
description: 'Renders streamed markdown from an AI model with Eufemia elements.'
version: 11.17.0
generatedAt: 2026-11-05T09:55:47.093Z
checksum: e240f26fd372829820a263c19252e1a9feb225fbb37810398cd6ca420311e7ae
---

# Ai.Response

**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 { Response } from '@dnb/eufemia/extensions/ai'
import '@dnb/eufemia/extensions/ai/style'
render(<Response />)
```

## Description

Images are disabled by default. Set `allowedImagePrefixes` to trusted origins when your application needs them. Ambiguous single-dot amounts such as `11.725 NOK` stay as written in Norwegian and Swedish; Danish dot grouping remains supported.

`Ai.Response` renders markdown from an AI model with Eufemia elements, such as [P](/web/elements/paragraph/), [Heading](/web/components/heading/), [Lists](/web/elements/lists/), [Code](/web/elements/code/), [Anchor](/web/components/anchor/) and [Table](/web/components/table/). It has no extra dependencies.

It is made for streaming:

- Unfinished markdown in the last block, like `**bold` or an open code span, is closed while it streams in. Unfinished links and images are hidden until they are complete.
- Blocks that are finished are not rendered again when new text arrives.

It is safe by default:

- Raw HTML is never rendered, only shown as text.
- Links and images with unsafe URLs (like `javascript:`) are rendered as text. Use `allowedLinkPrefixes` and `allowedImagePrefixes` to only allow known URLs.

Supported markdown: headings, paragraphs, emphasis, strikethrough, inline code, fenced code blocks, ordered, unordered and task lists, blockquotes, thematic breaks, tables, links, autolinks and images.

Code blocks use [CopyOnClick.Button](/web/components/copy-on-click/) to copy the code with a mouse, touch, or keyboard. The button keeps its translated "Copy code" label and announces the translated confirmation in a tooltip only when copying succeeds.

## Currency amounts

Recognized currency amounts use [NumberFormat.Currency](/web/components/number-format/) automatically, including in emphasis, lists and table cells. They inherit the Provider's display locale and NumberFormat settings, including the currency display. With Norwegian defaults, `1 234 kr` is displayed as `1 234 kr`. The number of written decimals is preserved. Formatting does not verify whether an AI-generated amount is correct.

Supported uppercase codes are `NOK`, `SEK`, `DKK`, `ISK`, `EUR`, `USD`, `GBP`, `CHF`, `JPY`, `CAD`, `AUD`, `NZD`, `CNY`, `HKD`, `SGD`, `INR` and `PLN`, before or after an amount. `€` identifies EUR and `£` identifies GBP. `kr` uses the Provider currency only when it is NOK, SEK, DKK or ISK. Bare numbers, `$`, unsupported markers, malformed amounts and values that would lose numeric precision remain unchanged. Code, link labels, URLs, image alternatives and HTML attributes are not formatted.

The source locale controls decimal and grouping separators. It defaults to the Provider locale. Standard three-digit grouping with Unicode space separators and the locale's grouping separator is supported. Dot grouping is also supported for comma-decimal locales. Amounts with more than two fractional digits remain text.

Recognition is conservative. A prefix amount followed by a word stays text, including across emphasis. This preserves expressions such as `NOK 1,5 milliarder`, `EUR 1,5 millones` and ordinary prose such as `NOK 150 betales nå.` without trying to interpret the following word. A complete prefix amount such as `NOK 150.` or a suffix amount such as `150 kr betales nå.` can be formatted. Recognition stays within a single text segment, so `**100 NOK**` works while `**100** NOK` stays text. This is a limited text recognizer, not a financial expression parser.

If the generated text uses another locale, set `currencyFormatting={{ locale: 'en-GB' }}`. You can also provide `currency` to interpret `kr`, for example `currencyFormatting={{ locale: 'sv-SE', currency: 'SEK' }}`. An invalid source locale leaves amounts unchanged. Set `currencyFormatting={false}` to preserve all original currency text.

While streaming, an amount at the end of unfinished text remains unformatted until following content confirms its boundary. Whitespace alone is not enough because it could be a grouping separator. Set `parseIncompleteMarkdown={false}` when streaming finishes, stops or fails so a final amount can be formatted. Keep this setting specific to each message.

When the application already knows the amount and currency, render [NumberFormat.Currency](/web/components/number-format/) directly from those values. Prefer this for financial content rather than extracting values from generated prose. See the [structured amount demo](/web/patterns-and-extensions/ai/Response/demos/#structured-amount).

Not supported: syntax highlighting, math, diagrams, indented code blocks, HTML and HTML entities. You can add syntax highlighting by overriding the `code` element with `components`. Code blocks get a `language-*` class.

## Horizontal table overflow

Markdown tables use `Table.ScrollView`. If a table is wider than its available space, a translated hint below it explains that it can be scrolled horizontally. The hint is associated with the scroll area for assistive technology and disappears when all columns fit. Keyboard scrolling remains available. The hint also updates when streaming changes the table or the container resizes.

| Key             | nb-NO                                | en-GB                                     | sv-SE                                   | da-DK                               |
| --------------- | ------------------------------------ | ----------------------------------------- | --------------------------------------- | ----------------------------------- |
| `tableOverflow` | Rull vannrett for å se hele tabellen | Scroll horizontally to see the full table | Rulla i sidled för att se hela tabellen | Rul vandret for at se hele tabellen |


## Demos

### Response


```tsx
render(<Response>{answer}</Response>)
```


### Wide tables

Tables retain horizontal scrolling and keyboard access. A translated hint appears below the table only when some columns are outside the visible area. Resize the example to see the hint appear or disappear.


```tsx
render(<div style={{
  maxWidth: '18rem'
}}>
        <Response parseIncompleteMarkdown={false}>
          {['| Category | This month | Last month | Difference |', '| :-- | --: | --: | --: |', '| Groceries | 4 200 kr | 3 900 kr | 300 kr |', '| Transport | 1 100 kr | 1 300 kr | -200 kr |'].join('\n')}
        </Response>
      </div>)
```


### Streaming response

Unfinished markdown is closed while the text streams in. Finished blocks are not rendered again.


```tsx
const StreamingResponse = () => {
  const [length, setLength] = useState(0);
  useEffect(() => {
    if (length >= answer.length) {
      return; // stop here
    }
    const timeout = setTimeout(() => setLength(current => current + 3), 30);
    return () => clearTimeout(timeout);
  }, [length]);
  return <>
              <Button variant="secondary" bottom onClick={() => setLength(0)}>
                Replay
              </Button>
              <Response>{answer.slice(0, length)}</Response>
            </>;
};
render(<StreamingResponse />);
```


### Formatted currency amounts

Explicit currencies in text, emphasis and table cells use `NumberFormat.Currency`. Bare numbers and ambiguous symbols remain text.

Prefix amounts followed by a word also remain text. This includes scaled amounts in any language and ordinary prose such as `NOK 150 betales nå.` Suffix amounts such as `150 kr betales nå.` can still be formatted.


```tsx
render(<Provider locale="nb-NO">
        <Response parseIncompleteMarkdown={false}>
          {currencyResponse}
        </Response>
      </Provider>)
```


### Source locale

Set the source locale when the response uses different separators from the display locale. This example reads English amounts and displays them in Norwegian.


```tsx
render(<Provider locale="nb-NO">
        <Response parseIncompleteMarkdown={false} currencyFormatting={{
    locale: 'en-GB'
  }}>
          {'The total is USD 1,234.50; the fee is EUR 99.95.'}
        </Response>
      </Provider>)
```


### Structured amount

When the application knows the amount and currency, render `NumberFormat.Currency` directly. This example uses a numeric value from structured data alongside generated prose, without extracting or interpreting the amount from the text.


```tsx
render(<Provider locale="nb-NO">
        <Message from="assistant">
          <Response currencyFormatting={false}>
            {'The confirmed amount is:'}
          </Response>
          <P>
            <NumberFormat.Currency value={responseAmount.value} currency={responseAmount.currency} decimals={0} />
          </P>
        </Message>
      </Provider>)
```


### Streaming currency amounts

An amount stays as text until following content confirms its boundary or streaming completes.


```tsx
const StreamingAmount = () => {
  const text = 'Your balance is NOK 12 400,50. The refund is 250,00 kr.';
  const [length, setLength] = useState(0);
  useEffect(() => {
    if (length >= text.length) {
      return undefined;
    }
    const timeout = setTimeout(() => setLength(length + 1), 100);
    return () => clearTimeout(timeout);
  }, [length, text]);
  return <Provider locale="nb-NO">
              <Button variant="secondary" bottom onClick={() => setLength(0)}>
                Replay
              </Button>
              <Response parseIncompleteMarkdown={length < text.length}>
                {text.slice(0, length)}
              </Response>
            </Provider>;
};
render(<StreamingAmount />);
```


### Images require a trusted origin

Images render as alternative text by default, without an image request. Applications can allow trusted image origins explicitly.


```tsx
render(<Response>
        {'![Untrusted image](https://untrusted.example/pixel.png)'}
      </Response>)
```


### Allowed URLs

Links and images that are not allowed are rendered as text.


```tsx
render(<Response allowedLinkPrefixes={['https://www.dnb.no']}>
        {'Read more on [dnb.no](https://www.dnb.no/), not on [example.com](https://example.com) or [this](javascript:alert(1)).'}
      </Response>)
```


### Custom elements

Use `components` to override how a markdown element is rendered.


```tsx
const Strong = ({
  children
}) => <strong style={{
  color: 'var(--color-sea-green)'
}}>
            {children}
          </strong>;
render(<Response components={{
  strong: Strong
}}>
            {'This answer uses a **custom** element for **bold** text.'}
          </Response>);
```

<!-- 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": {
    "currencyFormatting": {
      "doc": "Format recognized currency amounts with NumberFormat.Currency. Prefix amounts followed by a word remain text. Set to `false` to keep the original text, or provide the source `locale` and `currency` for amounts written as `kr`. Defaults to `true`, using Provider values.",
      "type": [
        "boolean",
        "object"
      ],
      "defaultValue": "true",
      "status": "optional"
    },
    "children": {
      "doc": "The markdown to render. Can be updated while the response is streaming.",
      "type": "string",
      "status": "optional"
    },
    "parseIncompleteMarkdown": {
      "doc": "Close unfinished markdown (like `**bold` or an open code span) and hide unfinished links and images in the last block while streaming.",
      "type": "boolean",
      "defaultValue": "true",
      "status": "optional"
    },
    "components": {
      "doc": "Override how a markdown element is rendered, keyed by its HTML tag name, e.g. `{ a: MyLink }`. Code blocks render `code` inside `pre` with a `language-*` class, and `pre` receives `code` and `language`.",
      "type": "object",
      "status": "optional"
    },
    "allowedLinkPrefixes": {
      "doc": "URL prefixes allowed for links. Use `*` to allow any http(s), mailto and tel URL. Links that are not allowed are rendered as text.",
      "type": "Array<string>",
      "defaultValue": "['*']",
      "status": "optional"
    },
    "allowedImagePrefixes": {
      "doc": "Trusted URL prefixes allowed for images. Images are disabled by default; blocked images render as alt text.",
      "type": "Array<string>",
      "defaultValue": "[]",
      "status": "optional"
    },
    "defaultOrigin": {
      "doc": "Origin used to resolve relative link and image URLs before they are checked.",
      "type": "string",
      "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          | nb-NO       | en-GB     | sv-SE       | da-DK       |
| ------------ | ----------- | --------- | ----------- | ----------- |
| `copyCode`   | Kopier kode | Copy code | Kopiera kod | Kopiér kode |
| `codeCopied` | Kopiert     | Copied    | Kopierat    | Kopieret    |
