# Empty state

Empty state templates explain why content is unavailable and guide people toward the next useful action across empty, error, no-permission, and no-result scenarios.

## Empty State

Source: CoreAI.

A compact empty state with a heading, two actions, and a supporting link.

[Download source](/coreai-design-system/source/examples/templates-empty-state--default.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CodeRegular } from "@fluentui/react-icons";
import { Link } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

export const Default = () => (<EmptyState header="Benefit from Action Artifact" actions={<>
          <Button appearance="primary">Action</Button>
          <Button icon={<CodeRegular />}>Action in code</Button>
        </>} links={<Link href="#">How to make the coolest artifact ever</Link>}/>);

export default Default;
```

## Implementation

CoreAI 0.12.0; Fluent 9.74.9.

[Setup and dependencies](/coreai-design-system/guides/Package.md).

Copy this template into your app. It is not an npm export. Adapt actions and navigation to your product.

## Error State

Source: CoreAI.

Error state: data failed to load. Provide a primary retry action so the user
can recover in place without reloading the full page.

[Download source](/coreai-design-system/source/examples/templates-empty-state--error-state.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { Illicon } from "@coreai-ds/react-components";
import type { DotGridData } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

// Generated once with iconToDotGrid at each source icon's native 24 x 24 resolution.
const illustrations = {
    agents: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000011111001110000000',
            '000000111111011111000000',
            '000001111111011011100000',
            '000001111110010011100000',
            '000011111110010011110000',
            '000011111110110111110000',
            '000111111100100111111000',
            '001111111100100111111000',
            '001111111101101111111100',
            '001111111001001111111100',
            '001111111001001111111100',
            '000111111011011111111000',
            '000011110011011111110000',
            '000011110010011111110000',
            '000001110110111111100000',
            '000001100110111111100000',
            '000000111100111111000000',
            '000000011001111110000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    search: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000001111110000000000',
            '000000111111111100000000',
            '000001110000001110000000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000001110000001111000000',
            '000000111111111111100000',
            '000000001111110001110000',
            '000000000000000000111000',
            '000000000000000000011100',
            '000000000000000000001100',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    lock: {
        rows: [
            '000000000000000000000000',
            '000000000011110000000000',
            '000000001111111100000000',
            '000000001100001100000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000001111111111111000000',
            '000001111111111111100000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000001100000110000',
            '000011000001100000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000001111111111111100000',
            '000000111111111111000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    error: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000111111000000000',
            '000000011111111110000000',
            '000001110000000011100000',
            '000011100000000001110000',
            '000011000000000000110000',
            '000110000001100000011000',
            '000100000001100000001000',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000000000000001100',
            '001100000000000000001100',
            '000100000001100000001000',
            '000110000000000000011000',
            '000011000000000000110000',
            '000011100000000001110000',
            '000001110000000011100000',
            '000000011111111110000000',
            '000000000111111000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
} satisfies Record<string, DotGridData>;

export const ErrorState = () => (<EmptyState illustration={<Illicon data={illustrations.error}/>} header="Something went wrong" description="An error occurred while loading this content." actions={<Button appearance="primary">Try again</Button>}/>);

export default ErrorState;
```

## No Permissions

Source: CoreAI.

No permissions: the signed-in user lacks access to this resource. Offer a
secondary request-access action rather than a primary creation action.

[Download source](/coreai-design-system/source/examples/templates-empty-state--no-permissions.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { Illicon } from "@coreai-ds/react-components";
import type { DotGridData } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

// Generated once with iconToDotGrid at each source icon's native 24 x 24 resolution.
const illustrations = {
    agents: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000011111001110000000',
            '000000111111011111000000',
            '000001111111011011100000',
            '000001111110010011100000',
            '000011111110010011110000',
            '000011111110110111110000',
            '000111111100100111111000',
            '001111111100100111111000',
            '001111111101101111111100',
            '001111111001001111111100',
            '001111111001001111111100',
            '000111111011011111111000',
            '000011110011011111110000',
            '000011110010011111110000',
            '000001110110111111100000',
            '000001100110111111100000',
            '000000111100111111000000',
            '000000011001111110000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    search: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000001111110000000000',
            '000000111111111100000000',
            '000001110000001110000000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000001110000001111000000',
            '000000111111111111100000',
            '000000001111110001110000',
            '000000000000000000111000',
            '000000000000000000011100',
            '000000000000000000001100',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    lock: {
        rows: [
            '000000000000000000000000',
            '000000000011110000000000',
            '000000001111111100000000',
            '000000001100001100000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000001111111111111000000',
            '000001111111111111100000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000001100000110000',
            '000011000001100000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000001111111111111100000',
            '000000111111111111000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    error: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000111111000000000',
            '000000011111111110000000',
            '000001110000000011100000',
            '000011100000000001110000',
            '000011000000000000110000',
            '000110000001100000011000',
            '000100000001100000001000',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000000000000001100',
            '001100000000000000001100',
            '000100000001100000001000',
            '000110000000000000011000',
            '000011000000000000110000',
            '000011100000000001110000',
            '000001110000000011100000',
            '000000011111111110000000',
            '000000000111111000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
} satisfies Record<string, DotGridData>;

export const NoPermissions = () => (<EmptyState illustration={<Illicon data={illustrations.lock}/>} header="Access required" description="You don't have permission to view this content." actions={<Button appearance="secondary">Request access</Button>}/>);

export default NoPermissions;
```

## No Results

Source: CoreAI.

No results: a search or filter returned zero matches. Inform the user what
was searched and suggest how to recover without adding an unnecessary CTA.

[Download source](/coreai-design-system/source/examples/templates-empty-state--no-results.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { Illicon } from "@coreai-ds/react-components";
import type { DotGridData } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

// Generated once with iconToDotGrid at each source icon's native 24 x 24 resolution.
const illustrations = {
    agents: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000011111001110000000',
            '000000111111011111000000',
            '000001111111011011100000',
            '000001111110010011100000',
            '000011111110010011110000',
            '000011111110110111110000',
            '000111111100100111111000',
            '001111111100100111111000',
            '001111111101101111111100',
            '001111111001001111111100',
            '001111111001001111111100',
            '000111111011011111111000',
            '000011110011011111110000',
            '000011110010011111110000',
            '000001110110111111100000',
            '000001100110111111100000',
            '000000111100111111000000',
            '000000011001111110000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    search: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000001111110000000000',
            '000000111111111100000000',
            '000001110000001110000000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000001110000001111000000',
            '000000111111111111100000',
            '000000001111110001110000',
            '000000000000000000111000',
            '000000000000000000011100',
            '000000000000000000001100',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    lock: {
        rows: [
            '000000000000000000000000',
            '000000000011110000000000',
            '000000001111111100000000',
            '000000001100001100000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000001111111111111000000',
            '000001111111111111100000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000001100000110000',
            '000011000001100000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000001111111111111100000',
            '000000111111111111000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    error: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000111111000000000',
            '000000011111111110000000',
            '000001110000000011100000',
            '000011100000000001110000',
            '000011000000000000110000',
            '000110000001100000011000',
            '000100000001100000001000',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000000000000001100',
            '001100000000000000001100',
            '000100000001100000001000',
            '000110000000000000011000',
            '000011000000000000110000',
            '000011100000000001110000',
            '000001110000000011100000',
            '000000011111111110000000',
            '000000000111111000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
} satisfies Record<string, DotGridData>;

export const NoResults = () => (<EmptyState illustration={<Illicon data={illustrations.search}/>} header="No results found" description="Try adjusting your search or filters to find what you're looking for."/>);

export default NoResults;
```

## With Action Cards

Source: CoreAI.

Uses cards for separate onboarding paths. Each card owns its description and
actions, while the supporting link remains common to the empty state.

[Download source](/coreai-design-system/source/examples/templates-empty-state--with-action-cards.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { Link } from "@coreai-ds/react-components";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { Card } from "@coreai-ds/react-components";
import { CardHeader } from "@coreai-ds/react-components";
import { CardFooter } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CodeRegular } from "@fluentui/react-icons";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

export const WithActionCards = () => {
    const actions = [
        {
            heading: 'Connect data sources',
            description: 'Integrate with your enterprise data from supported sources like Fabric, Cosmos, Blob storage, and more.',
            action: 'Connect',
        },
        {
            heading: 'Upload files',
            description: 'Add files that can provide context for any agent in your project.',
            action: 'Upload',
        },
        {
            heading: 'Integrate Microsoft IQ',
            description: (<>
            Set up IQ integration for full business intelligence in your agents.{' '}
            <Link href="#">What can Work IQ do?</Link>
          </>),
            action: 'Integrate',
        },
    ];
    const styles = /* css */ `
      .empty-state-actions-example__card {
        box-sizing: border-box;
        display: flex;
        flex: 0 0 auto;
        width: 256px;
        height: 148px;
        text-align: left;
      }

      .empty-state-actions-example__header {
        flex: 1;
        align-content: start;
        row-gap: var(--spacingVerticalXS);
      }

      .empty-state-actions-example__footer {
        gap: var(--spacingHorizontalXS);
      }
    `;
    return (<>
        <style>{styles}</style>
        <EmptyState header="Benefit from Action Artifact" cards={<>
              {actions.map(({ heading, description, action }) => (<Card className="empty-state-actions-example__card" key={heading}>
                  <CardHeader className="empty-state-actions-example__header" header={<Text preset="heading5" foreground={1}>
                        {heading}
                      </Text>} description={<Text preset="bodyS">{description}</Text>}/>
                  <CardFooter className="empty-state-actions-example__footer">
                    <Button>{action}</Button>
                    <Button appearance="transparent" icon={<CodeRegular />}>
                      Use code
                    </Button>
                  </CardFooter>
                </Card>))}
            </>} links={<Link href="#">How to make the coolest artifact ever</Link>}/>
      </>);
};

export default WithActionCards;
```

## With Choice Cards

Source: CoreAI.

Uses the cards slot for a responsive group of selectable outcomes. The slot
accepts complete controls, so product teams own selection state and content.

[Download source](/coreai-design-system/source/examples/templates-empty-state--with-choice-cards.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { RadioGroup } from "@coreai-ds/react-components";
import { Card } from "@coreai-ds/react-components";
import { Radio } from "@coreai-ds/react-components";
import { CardHeader } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CodeRegular } from "@fluentui/react-icons";
import { Link } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

export const WithChoiceCards = () => {
    const options = [
        {
            value: 'quickstart',
            heading: 'Quickstart',
            description: 'Build from a guided starting point.',
        },
        {
            value: 'specific',
            heading: 'Specific outcome',
            description: 'Create a focused solution for one task.',
        },
        {
            value: 'advanced',
            heading: 'Advanced setup',
            description: 'Configure each part of the solution.',
        },
    ];
    const idPrefix = useId('empty-state-choice-title');
    const groupLabelId = useId('empty-state-choice-label');
    const [selectedValue, setSelectedValue] = React.useState('quickstart');
    const [submittedValue, setSubmittedValue] = React.useState('');
    const onChange = React.useCallback((_: React.FormEvent<HTMLDivElement>, data: {
        value: string;
    }) => setSelectedValue(data.value), []);
    const onSelectionChange = React.useCallback((value: string) => () => setSelectedValue(value), []);
    const onSubmit = React.useCallback((event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault();
        setSubmittedValue(selectedValue);
    }, [selectedValue]);
    const styles = /* css */ `
      .empty-state-choice-example {
        width: 100%;
      }

      .empty-state-choice-example__choices {
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: var(--spacingVerticalS);
        width: 100%;
      }

      .empty-state-choice-example__group {
        display: flex;
        flex: 1 1 100%;
        justify-content: center;
        gap: var(--spacingHorizontalL);
        width: 100%;
        max-width: 800px;
      }

      .empty-state-choice-example__card {
        box-sizing: border-box;
        display: flex;
        flex: 0 0 auto;
        width: 256px;
        min-height: 84px;
        text-align: left;
      }

      .empty-state-choice-example__header {
        flex: 1;
        min-width: 0;
        row-gap: var(--spacingVerticalXXS);
      }

      @media (max-width: 848px) {
        .empty-state-choice-example__group {
          flex-direction: column;
          align-items: center;
        }
      }
    `;
    return (<>
        <style>{styles}</style>
        <form className="empty-state-choice-example" onSubmit={onSubmit}>
          <EmptyState header="What would you like to create?" description="Choose the outcome that best matches your goal." cards={<div className="empty-state-choice-example__choices">
                <Text id={groupLabelId} weight="semibold">
                  Creation method
                </Text>
                <RadioGroup name="creation-method" value={selectedValue} onChange={onChange} layout="horizontal" className="empty-state-choice-example__group" aria-labelledby={groupLabelId}>
                  {options.map(({ value, heading, description }) => {
                const titleId = `${idPrefix}-${value}`;
                return (<Card key={value} className="empty-state-choice-example__card" floatingActionPosition="left" floatingAction={<Radio value={value} aria-labelledby={titleId}/>} selected={selectedValue === value} onSelectionChange={onSelectionChange(value)}>
                        <CardHeader className="empty-state-choice-example__header" header={<Text id={titleId} preset="heading5" foreground={1}>
                              {heading}
                            </Text>} description={<Text preset="bodyS">{description}</Text>}/>
                      </Card>);
            })}
                </RadioGroup>
              </div>} actions={<>
                <Button appearance="primary" type="submit">
                  Continue
                </Button>
                <Button type="button" icon={<CodeRegular />}>
                  Action in code
                </Button>
                {submittedValue ? (<Text role="status">Selected method: {submittedValue}</Text>) : null}
              </>} links={<Link href="#">Compare creation methods</Link>}/>
        </form>
      </>);
};

export default WithChoiceCards;
```

## With Detail Cards

Source: CoreAI.

Uses two equal-width cards with the Details List template for comparing
Before and Upsell options, including improvement indicators, before taking action.

[Download source](/coreai-design-system/source/examples/templates-empty-state--with-detail-cards.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { Text } from "@coreai-ds/react-components";
import { ArrowUpRegular } from "@fluentui/react-icons";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Card } from "@coreai-ds/react-components";
import { CardHeader } from "@coreai-ds/react-components";
import { Badge } from "@coreai-ds/react-components";
import { Divider } from "@coreai-ds/react-components";
import { mergeClasses } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CodeRegular } from "@fluentui/react-icons";
import { Link } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

type DetailsItem = {
    key: React.Key;
    label: React.ReactNode;
    /** The value to show. If there is no value, leave this detail out of the list. */
    value: React.ReactNode;
};

interface DetailsListProps extends Omit<React.ComponentPropsWithoutRef<'dl'>, 'children'> {
    items: DetailsItem[];
    itemClassName?: string;
    labelClassName?: string;
    valueClassName?: string;
}

// ─── Styles ──────────────────────────────────────────────────────────────────
const useStyles = makeStyles({
    // ── Root container ─────────────────────────────────────────────────────────
    root: {
        display: 'flex',
        flexDirection: 'column',
        rowGap: tokens.spacingVerticalL,
        margin: 0,
        padding: 0,
        width: '100%',
    },
    // ── Row layout ─────────────────────────────────────────────────────────────
    item: {
        display: 'flex',
        flexDirection: 'column',
        rowGap: tokens.spacingVerticalXXS,
    },
    // ── Label / value text ────────────────────────────────────────────────────
    label: {
        margin: 0,
    },
    value: {
        margin: 0,
    },
});

// ─── Component ────────────────────────────────────────────────────────────────
function DetailsList({ items, className, itemClassName, labelClassName, valueClassName, ...rest }: DetailsListProps) {
    const styles = useStyles();
    return (<dl {...rest} className={mergeClasses('fui-DetailsList', styles.root, className)}>
      {items.map((item) => (<div key={item.key} className={mergeClasses('fui-DetailsList__item', styles.item, itemClassName)}>
          <dt className={mergeClasses('fui-DetailsList__label', styles.label, labelClassName)}>
            {typeof item.label === 'string' || typeof item.label === 'number' ? (<Text preset="bodySStrong" foreground={2}>
                {item.label}
              </Text>) : (item.label)}
          </dt>
          <dd className={mergeClasses('fui-DetailsList__value', styles.value, valueClassName)}>
            {typeof item.value === 'string' || typeof item.value === 'number' ? (<Text preset="body" foreground={1}>
                {item.value}
              </Text>) : (item.value)}
          </dd>
        </div>))}
    </dl>);
}

export const WithDetailCards = () => {
    const options = [
        { label: 'Before', isUpsell: false },
        { label: 'Upsell', isUpsell: true },
    ].map((option) => ({
        ...option,
        items: ['first', 'second', 'third'].map((key) => ({
            key,
            label: <Text preset="bodyS">Primary text slot</Text>,
            value: (<span className="empty-state-details-example__value">
            <Text preset="bodyS" foreground={3}>
              00:00 AM
            </Text>
            {option.isUpsell && (<Text preset="bodyS" className="empty-state-details-example__improvement" aria-label="97% increase">
                <ArrowUpRegular aria-hidden="true"/>
                97%
              </Text>)}
          </span>),
        })),
    }));
    const styles = /* css */ `
      .empty-state-details-example__card {
        box-sizing: border-box;
        flex: 0 0 auto;
        width: 392px;
        max-width: 100%;
        height: 197px;
        gap: var(--spacingVerticalS);
        padding: var(--spacingVerticalL) var(--spacingHorizontalL);
        text-align: left;
      }

      .empty-state-details-example__list {
        row-gap: var(--spacingVerticalS);
        line-height: var(--lineHeightBase200);
      }

      .empty-state-details-example__card--upsell {
        border-color: var(--colorBrandStroke1);
      }

      .empty-state-details-example__value {
        display: flex;
        align-items: center;
        gap: var(--spacingHorizontalM);
      }

      .empty-state-details-example__improvement {
        display: inline-flex;
        align-items: center;
        gap: var(--spacingHorizontalXXS);
        color: var(--colorStatusSuccessForeground1);
      }

      .empty-state-details-example__row {
        display: flex;
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: var(--spacingHorizontalM);
      }

      .empty-state-details-example__header {
        row-gap: var(--spacingVerticalXXS);
      }

      .empty-state-details-example__option {
        white-space: nowrap;
      }
    `;
    return (<>
        <style>{styles}</style>
        <EmptyState header="Benefit from Action Artifact" cards={<>
              {options.map(({ label, isUpsell, items }) => (<Card className={`empty-state-details-example__card${isUpsell ? ' empty-state-details-example__card--upsell' : ''}`} key={label}>
                  <CardHeader className="empty-state-details-example__header" header={<Text preset="heading5" foreground={1}>
                        Card title
                      </Text>} description={<Text preset="bodyS">Additional metadata</Text>} action={<Badge appearance="tint" color={isUpsell ? 'brand' : 'subtle'} shape="rounded" className="empty-state-details-example__option">
                        {label}
                      </Badge>}/>
                  <Divider />
                  <DetailsList items={items} className="empty-state-details-example__list" itemClassName="empty-state-details-example__row"/>
                </Card>))}
            </>} actions={<>
              <Button appearance="primary">Action</Button>
              <Button icon={<CodeRegular />}>Action in code</Button>
            </>} links={<Link href="#">How to make the coolest artifact ever</Link>}/>
      </>);
};

export default WithDetailCards;
```

## Empty State with Illustration

Source: CoreAI.

Adds a saved-data Illicon and supporting description to the standard empty-state
actions and link. The 24-cell grid renders at 120px with the default 5px pitch.

[Download source](/coreai-design-system/source/examples/templates-empty-state--with-illustration.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { Illicon } from "@coreai-ds/react-components";
import type { DotGridData } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CodeRegular } from "@fluentui/react-icons";
import { Link } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

// Generated once with iconToDotGrid at each source icon's native 24 x 24 resolution.
const illustrations = {
    agents: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000011111001110000000',
            '000000111111011111000000',
            '000001111111011011100000',
            '000001111110010011100000',
            '000011111110010011110000',
            '000011111110110111110000',
            '000111111100100111111000',
            '001111111100100111111000',
            '001111111101101111111100',
            '001111111001001111111100',
            '001111111001001111111100',
            '000111111011011111111000',
            '000011110011011111110000',
            '000011110010011111110000',
            '000001110110111111100000',
            '000001100110111111100000',
            '000000111100111111000000',
            '000000011001111110000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    search: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000000000000000000',
            '000000001111110000000000',
            '000000111111111100000000',
            '000001110000001110000000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000110000000000001100000',
            '000011000000000011000000',
            '000011000000000011000000',
            '000001110000001111000000',
            '000000111111111111100000',
            '000000001111110001110000',
            '000000000000000000111000',
            '000000000000000000011100',
            '000000000000000000001100',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    lock: {
        rows: [
            '000000000000000000000000',
            '000000000011110000000000',
            '000000001111111100000000',
            '000000001100001100000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000000011000000110000000',
            '000001111111111111000000',
            '000001111111111111100000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000001100000110000',
            '000011000001100000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000011000000000000110000',
            '000001111111111111100000',
            '000000111111111111000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
    error: {
        rows: [
            '000000000000000000000000',
            '000000000000000000000000',
            '000000000111111000000000',
            '000000011111111110000000',
            '000001110000000011100000',
            '000011100000000001110000',
            '000011000000000000110000',
            '000110000001100000011000',
            '000100000001100000001000',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000001100000001100',
            '001100000000000000001100',
            '001100000000000000001100',
            '000100000001100000001000',
            '000110000000000000011000',
            '000011000000000000110000',
            '000011100000000001110000',
            '000001110000000011100000',
            '000000011111111110000000',
            '000000000111111000000000',
            '000000000000000000000000',
            '000000000000000000000000',
        ],
    },
} satisfies Record<string, DotGridData>;

export const WithIllustration = () => (<EmptyState illustration={<Illicon data={illustrations.agents}/>} header="Benefit from Action Artifact" description="Disambiguate with text or exclude entirely." actions={<>
          <Button appearance="primary">Create agent</Button>
          <Button icon={<CodeRegular />}>Code</Button>
        </>} links={<Link href="#">How to make the coolest artifact ever</Link>}/>);

export default WithIllustration;
```

## With Metrics

Source: CoreAI.

Adds a row of value propositions or metrics while retaining the standard
message, actions, and supporting link.

[Download source](/coreai-design-system/source/examples/templates-empty-state--with-metrics.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { Text } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { CodeRegular } from "@fluentui/react-icons";
import { Link } from "@coreai-ds/react-components";

interface EmptyStateProps {
    /** Optional Illicon or custom artwork displayed above the message. */
    illustration?: React.ReactNode;
    /** Main empty-state heading. */
    header: React.ReactNode;
    /** Optional supporting text explaining the state or the next step. */
    description?: React.ReactNode;
    /** Optional row of value propositions, metrics, or highlights. */
    metrics?: React.ReactNode;
    /** Optional row of cards, choices, integrations, or detailed content. */
    cards?: React.ReactNode;
    /** Optional row of primary and secondary actions. */
    actions?: React.ReactNode;
    /** Optional supporting links displayed after the actions. */
    links?: React.ReactNode;
}

const useEmptyStateStyles = makeStyles({
    root: {
        boxSizing: 'border-box',
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        justifyContent: 'center',
        rowGap: tokens.spacingVerticalXL,
        width: '100%',
        minHeight: '400px', // no dimension token; preserves the empty-state canvas height
        paddingBlock: tokens.spacingVerticalXXXL,
        paddingInline: tokens.spacingHorizontalXXL,
        textAlign: 'center',
    },
    illustration: {
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        color: tokens.colorNeutralForeground3,
        fontSize: tokens.fontSizeHero800,
        lineHeight: 1,
    },
    copy: {
        display: 'flex',
        flexDirection: 'column',
        alignItems: 'center',
        rowGap: tokens.spacingVerticalXS,
        maxWidth: '600px', // no dimension token; matches the Figma copy width
    },
    header: {
        marginBlock: 0,
    },
    description: {
        marginBlock: 0,
    },
    collection: {
        display: 'flex',
        flexWrap: 'wrap',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalL,
        width: '100%',
    },
    actions: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
        width: '100%',
    },
    links: {
        display: 'flex',
        flexWrap: 'wrap',
        alignItems: 'center',
        justifyContent: 'center',
        gap: tokens.spacingHorizontalS,
    },
});

function EmptyState({ illustration, header, description, metrics, cards, actions, links, }: EmptyStateProps) {
    const styles = useEmptyStateStyles();
    return (<div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (<Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>);
}

export const WithMetrics = () => {
    const metrics = ['Fast setup', 'Flexible tools', 'Built-in guidance'];
    const styles = /* css */ `
      .empty-state-metrics-example__metric {
        box-sizing: border-box;
        display: flex;
        flex: 0 0 200px;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        width: 200px;
        height: 72px;
        padding: var(--spacingVerticalM) var(--spacingHorizontalL);
        border-radius: var(--borderRadiusMedium);
        background: var(--colorNeutralBackground2);
        text-align: center;
      }

    `;
    return (<>
        <style>{styles}</style>
        <EmptyState header="Benefit from Action Artifact" description="Disambiguate with text or exclude entirely." metrics={<>
              {metrics.map((label) => (<div className="empty-state-metrics-example__metric" key={label}>
                  <Text preset="bodySStrong" style={{ color: tokens.colorPaletteGreenForeground1 }}>
                    Value prop
                  </Text>
                  <Text preset="bodyStrong">{label}</Text>
                  <Text preset="bodyS">Metric label</Text>
                </div>))}
            </>} actions={<>
              <Button appearance="primary">Action</Button>
              <Button icon={<CodeRegular />}>Action in code</Button>
            </>} links={<Link href="#">How to make the coolest artifact ever</Link>}/>
      </>);
};

export default WithMetrics;
```

## Template source: EmptyState.tsx

[Download EmptyState.tsx](/coreai-design-system/source/templates/empty-state/EmptyState.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
 * Templates / Empty State
 *
 * Composable empty-state pattern for pages and data surfaces that have no
 * content to display. Supply only the slots needed for the use case, from a
 * compact message through to metrics, cards, actions, and supporting links.
 * Use Illicon with saved grid data for illustrations; the slot also accepts
 * custom artwork without converting it during rendering.
 *
 * Copy-paste starting point — not a published, importable component. Copy
 * this file into your codebase and adjust as needed.
 */

import * as React from 'react';
import { makeStyles, tokens, Text } from '@coreai-ds/react-components';

export const useEmptyStateStyles = makeStyles({
  root: {
    boxSizing: 'border-box',
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    justifyContent: 'center',
    rowGap: tokens.spacingVerticalXL,
    width: '100%',
    minHeight: '400px', // no dimension token; preserves the empty-state canvas height
    paddingBlock: tokens.spacingVerticalXXXL,
    paddingInline: tokens.spacingHorizontalXXL,
    textAlign: 'center',
  },
  illustration: {
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    color: tokens.colorNeutralForeground3,
    fontSize: tokens.fontSizeHero800,
    lineHeight: 1,
  },
  copy: {
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    rowGap: tokens.spacingVerticalXS,
    maxWidth: '600px', // no dimension token; matches the Figma copy width
  },
  header: {
    marginBlock: 0,
  },
  description: {
    marginBlock: 0,
  },
  collection: {
    display: 'flex',
    flexWrap: 'wrap',
    justifyContent: 'center',
    gap: tokens.spacingHorizontalL,
    width: '100%',
  },
  actions: {
    display: 'flex',
    flexWrap: 'wrap',
    alignItems: 'center',
    justifyContent: 'center',
    gap: tokens.spacingHorizontalS,
    width: '100%',
  },
  links: {
    display: 'flex',
    flexWrap: 'wrap',
    alignItems: 'center',
    justifyContent: 'center',
    gap: tokens.spacingHorizontalS,
  },
});

export interface EmptyStateProps {
  /** Optional Illicon or custom artwork displayed above the message. */
  illustration?: React.ReactNode;
  /** Main empty-state heading. */
  header: React.ReactNode;
  /** Optional supporting text explaining the state or the next step. */
  description?: React.ReactNode;
  /** Optional row of value propositions, metrics, or highlights. */
  metrics?: React.ReactNode;
  /** Optional row of cards, choices, integrations, or detailed content. */
  cards?: React.ReactNode;
  /** Optional row of primary and secondary actions. */
  actions?: React.ReactNode;
  /** Optional supporting links displayed after the actions. */
  links?: React.ReactNode;
}

export function EmptyState({
  illustration,
  header,
  description,
  metrics,
  cards,
  actions,
  links,
}: EmptyStateProps) {
  const styles = useEmptyStateStyles();
  return (
    <div className={styles.root}>
      {illustration ? <div className={styles.illustration}>{illustration}</div> : null}

      <div className={styles.copy}>
        <Text as="h2" preset="heading4" foreground={1} className={styles.header}>
          {header}
        </Text>
        {description ? (
          <Text as="p" preset="body" foreground={2} className={styles.description}>
            {description}
          </Text>
        ) : null}
      </div>

      {metrics ? <div className={styles.collection}>{metrics}</div> : null}
      {cards ? <div className={styles.collection}>{cards}</div> : null}
      {actions ? <div className={styles.actions}>{actions}</div> : null}
      {links ? <div className={styles.links}>{links}</div> : null}
    </div>
  );
}
```

## Template source: DetailsList.tsx

[Download DetailsList.tsx](/coreai-design-system/source/templates/empty-state/DetailsList.tsx)

```tsx
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

/**
 * Templates / Details List
 *
 * Starter scaffold for building a label-value details panel with CoreAI
 * primitives. Keep the semantic <dl>/<dt>/<dd> structure, replace the sample
 * items with your own data, and optionally customize class hooks.
 *
 * Displays label-value pairs in a compact, readable layout.
 * Useful for user profiles, resource metadata, and summary panels.
 *
 * Copy-paste starting point — not an importable component.
 */

import * as React from 'react';
import { makeStyles, mergeClasses, Text, tokens } from '@coreai-ds/react-components';

export type DetailsItem = {
  key: React.Key;
  label: React.ReactNode;
  /** The value to show. If there is no value, leave this detail out of the list. */
  value: React.ReactNode;
};

// ─── Styles ──────────────────────────────────────────────────────────────────

const useStyles = makeStyles({
  // ── Root container ─────────────────────────────────────────────────────────
  root: {
    display: 'flex',
    flexDirection: 'column',
    rowGap: tokens.spacingVerticalL,
    margin: 0,
    padding: 0,
    width: '100%',
  },

  // ── Row layout ─────────────────────────────────────────────────────────────
  item: {
    display: 'flex',
    flexDirection: 'column',
    rowGap: tokens.spacingVerticalXXS,
  },

  // ── Label / value text ────────────────────────────────────────────────────
  label: {
    margin: 0,
  },
  value: {
    margin: 0,
  },
});

export interface DetailsListProps extends Omit<React.ComponentPropsWithoutRef<'dl'>, 'children'> {
  items: DetailsItem[];
  itemClassName?: string;
  labelClassName?: string;
  valueClassName?: string;
}

// ─── Component ────────────────────────────────────────────────────────────────

export function DetailsList({
  items,
  className,
  itemClassName,
  labelClassName,
  valueClassName,
  ...rest
}: DetailsListProps) {
  const styles = useStyles();

  return (
    <dl {...rest} className={mergeClasses('fui-DetailsList', styles.root, className)}>
      {items.map((item) => (
        <div
          key={item.key}
          className={mergeClasses('fui-DetailsList__item', styles.item, itemClassName)}
        >
          <dt className={mergeClasses('fui-DetailsList__label', styles.label, labelClassName)}>
            {typeof item.label === 'string' || typeof item.label === 'number' ? (
              <Text preset="bodySStrong" foreground={2}>
                {item.label}
              </Text>
            ) : (
              item.label
            )}
          </dt>
          <dd className={mergeClasses('fui-DetailsList__value', styles.value, valueClassName)}>
            {typeof item.value === 'string' || typeof item.value === 'number' ? (
              <Text preset="body" foreground={1}>
                {item.value}
              </Text>
            ) : (
              item.value
            )}
          </dd>
        </div>
      ))}
    </dl>
  );
}
```

## Resources

- [Figma component](https://www.figma.com/design/eXfSQrRg3poHRgmr0ivN5V/CoreAI-Figma-Library?node-id=435338-91619)