{
  "name": "EmptyState",
  "source": "src/components/EmptyState.tsx",
  "propsType": "EmptyStateProps",
  "description": "EmptyState — the state five of this system's nine patterns require and none\nof them could render.\n\n`DataTable`, `PageShell`, `SettingsPanel`, `AssistantAnswer` and\n`HumanApproval` all specify an empty state in their recipe. Until this\nexisted each consumer invented one, which is how a design system ends up\nwith four different ways to say \"nothing here\".\n\nREASON IS REQUIRED, AND HAS NO DEFAULT\n\nThe four reasons are genuinely different situations with different correct\nactions, and the failure mode is silent: an app that shows \"No invoices yet\n— Create your first invoice\" to a user whose filter excluded everything has\ntold them their records are gone. A default would make that the easy path,\nso there isn't one.\n\n`no-results` and `error` deliberately do NOT offer a create action in the\ncontract's guidance. The first needs the filter cleared; the second needs a\nretry, and offering \"Create\" over a failed fetch invites a duplicate.\n\nNOT A LIVE REGION\n\nAn empty state replaces content, so it is what the user reads next rather\nthan something announced over what they are reading. `role=\"status\"` here\nwould interrupt on every keystroke of a filter box. If a specific flow needs\nthe announcement, the caller owns that decision — the same reasoning\nStreamingText applies to `aria-live`.\n\n`error` is rendered as `role=\"alert\"` ONLY when the caller opts in via\n`role`, for the same reason: an error empty state arriving during typing is\nstill a read, not an interruption.\n\nNo `'use client'`: nothing here holds state. The buttons passed through\n`action` carry their own boundary.",
  "import": "import { EmptyState } from 'ionbase-ui';",
  "status": "stable",
  "summary": "The state a region shows when it has nothing to show, and why — first run, no results, no access, or a failed fetch.",
  "useWhen": [
    "a list, table, panel or route has no content to render",
    "a filter or search returns nothing",
    "the account is not permitted to see what would be here",
    "the request that would have filled the region failed"
  ],
  "useInstead": [
    {
      "when": "content exists and something went wrong beside it",
      "use": "Alert",
      "why": "an Alert sits with the content; an EmptyState replaces it. If there is a table on screen, the message about it is an Alert"
    },
    {
      "when": "the message is transient and the content is unaffected",
      "use": "Toast"
    }
  ],
  "slots": {
    "title": {
      "accepts": "text",
      "note": "required — it is the region's statement. Name the thing and the situation: \"No invoices match these filters\", not \"No results\""
    },
    "description": {
      "accepts": "text",
      "note": "one or two sentences: what would be here, and what puts it here"
    },
    "action": {
      "accepts": "Button",
      "note": "the action that RESOLVES this reason — clearing a filter for no-results, not creating a record"
    },
    "secondaryAction": {
      "accepts": "Button or Link",
      "note": "a lower-emphasis escape hatch — \"Contact an admin\""
    },
    "icon": {
      "accepts": "Icon",
      "note": "decorative only, rendered aria-hidden — the title already says what this is"
    },
    "children": {
      "accepts": "any",
      "note": "extra content between the description and the actions"
    }
  },
  "variants": {
    "reason": {
      "first-run": {
        "use": "nothing exists yet and the user has never created one",
        "note": "the only reason where a create action is the right primary"
      },
      "no-results": {
        "use": "records exist; the current filter or query matches none of them",
        "note": "the action is clearing the filter. Offering \"Create\" here tells a user with 400 records and a typo that their data is gone"
      },
      "no-access": {
        "use": "records exist and match, but this account may not see them",
        "note": "name the permission or plan. \"Nothing here\" for a permissions problem sends the user to support with the wrong question"
      },
      "error": {
        "use": "the request that would have filled the region failed",
        "note": "the action is a retry. Never a create — offering one over a failed fetch invites a duplicate record"
      }
    },
    "size": {
      "inline": {
        "use": "inside something that already has a frame — a table body, a card"
      },
      "panel": {
        "use": "a region of a page. The default"
      },
      "page": {
        "use": "a whole route with nothing in it"
      }
    }
  },
  "a11y": {
    "guarantees": [
      "the title renders as a real heading, so the region appears in the document outline",
      "the icon is aria-hidden, so a screen reader is not told the same thing twice"
    ],
    "requires": [
      "a `title` that states the situation, since it is what a screen-reader user hears in place of the missing content",
      "`headingLevel` matching the surrounding outline — h3 is the default, but only the page knows its real level"
    ],
    "notes": [
      "This is deliberately NOT a live region. An empty state replaces content, so it is what the user reads next rather than something announced over what they are reading — `role=\"status\"` here fires on every keystroke of a filter box. If a flow genuinely needs the announcement, pass `role` yourself.",
      "`headingLevel` takes 2-6 and defaults to 3. It is a number rather than a variant because it is an outline decision, not a visual one — the size prop is what changes how the title looks.",
      "`h1` is not available: an empty state describes a region, and the page's own title belongs to the page. Same call FullCard makes.",
      "Under forced-colors the reason tint is discarded, so the region draws a border to stay distinguishable from the content it replaced."
    ]
  },
  "antiPatterns": [
    {
      "dont": "using `first-run` for an empty filter result",
      "do": "no-results",
      "why": "it is the most common empty-state bug in enterprise software: a user with 400 invoices and a typo in their filter is told they have none, and offered a button to make one"
    },
    {
      "dont": "\"No data\" or \"Nothing found\" as the title",
      "why": "it names neither the thing nor the situation, so the user cannot tell whether to create a record, clear a filter, or ask for access"
    },
    {
      "dont": "an EmptyState while the request is still in flight",
      "why": "it asserts the data is absent before anything knows, then contradicts itself a moment later"
    },
    {
      "dont": "a create action on `error`",
      "why": "the fetch failed, so nobody knows whether the record already exists — this is how duplicates get made"
    },
    {
      "dont": "an icon with its own `aria-label` or visible caption",
      "why": "the slot is decorative and the title already carries the meaning; a labelled icon makes it announce twice"
    },
    {
      "dont": "hiding the region entirely when it is empty",
      "why": "a section that vanishes is indistinguishable from one that failed to render, and the user cannot tell that they are looking at everything there is"
    },
    {
      "dont": "an EmptyState above a form nobody has filled in yet",
      "why": "a form's empty state IS its initial state — see the Form pattern. A message saying the form is empty is noise on top of a form that is visibly empty"
    }
  ],
  "stylesheet": "src/styles/empty-state.css",
  "tokens": [
    "--font-family-sans",
    "--font-weight-semibold",
    "--icon-error",
    "--icon-primary",
    "--icon-size-md",
    "--icon-size-xl",
    "--icon-tertiary",
    "--icon-warning",
    "--ion-empty-state-gap",
    "--ion-empty-state-icon",
    "--ion-empty-state-icon-size",
    "--ion-empty-state-pad-block",
    "--ion-empty-state-title",
    "--ion-empty-state-title-line-height",
    "--radius-md",
    "--spacing-12",
    "--spacing-16",
    "--spacing-24",
    "--spacing-4",
    "--spacing-48",
    "--spacing-64",
    "--spacing-8",
    "--text-default",
    "--text-secondary",
    "--type-body",
    "--type-body-line-height",
    "--type-body-md",
    "--type-body-md-line-height",
    "--type-h3",
    "--type-h3-line-height",
    "--type-h4",
    "--type-h4-line-height"
  ],
  "props": {
    "reason": {
      "type": "EmptyStateReason",
      "required": true,
      "origin": "own",
      "description": "Why there is nothing here. Required, and there is no default: every\ndefault would be a guess, and the wrong guess is the bug this component\nexists to prevent.",
      "values": [
        "error",
        "first-run",
        "no-results",
        "no-access"
      ]
    },
    "title": {
      "type": "React.ReactNode",
      "required": true,
      "origin": "own",
      "description": "What is missing, in the user's words. Required — it is the accessible name\nof the region, and an empty state with no statement is a blank area with\npadding.\n\nName the thing and the situation: \"No invoices match these filters\", not\n\"No results\"."
    },
    "description": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "One or two sentences: what would be here, and what puts it here."
    },
    "action": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "The action that resolves this state. For `no-results` that is clearing the\nfilter, NOT creating a record — see `useInstead` in the contract."
    },
    "secondaryAction": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "A lower-emphasis escape hatch beside `action` — \"Contact an admin\"."
    },
    "icon": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "Decorative mark above the title. Rendered `aria-hidden`: the title already\nsays what this is, and an icon that repeats it announces twice."
    },
    "size": {
      "type": "EmptyStateSize | undefined",
      "required": false,
      "origin": "own",
      "description": "Defaults to `panel`.",
      "values": [
        "inline",
        "page",
        "panel"
      ]
    },
    "headingLevel": {
      "type": "EmptyStateHeadingLevel | undefined",
      "required": false,
      "origin": "own",
      "description": "Heading level for the title. Defaults to `h3`; only the page knows its own\noutline, so it is a prop rather than a constant."
    },
    "children": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own"
    }
  },
  "propCounts": {
    "own": 9,
    "aria": 0,
    "dom": 276,
    "other": 0
  }
}
