{
  "name": "StreamingText",
  "source": "src/components/StreamingText.tsx",
  "propsType": "StreamingTextProps",
  "description": "StreamingText — model output arriving a token at a time.\n\nIT IS NOT A LIVE REGION, AND THAT IS THE WHOLE DESIGN\n\nThe obvious implementation — `aria-live=\"polite\"` on the container — is the\none that makes a screen reader unusable. Every token mutation queues an\nannouncement, so the user hears the answer re-read, stuttered, dozens of\ntimes, and cannot get ahead of it. `aria-live=\"off\"` is not an oversight\nhere; it is the accessible choice.\n\nWhat it does instead: `aria-busy` while streaming, so assistive tech knows\nthe region is unsettled and can wait. The text is ordinary readable content\nthroughout — a screen-reader user navigates into it whenever they want,\nexactly like sighted users reading ahead of the cursor.\n\nANNOUNCING COMPLETION IS THE CALLER'S CALL, not this component's. Some\nsurfaces want \"response complete\"; a chat with ten turns on screen does not\nwant ten of them. Render your own `role=\"status\"` when you want it.\n\nTHE HEIGHT IS RESERVED, NOT ANIMATED. `minLines` holds space in `lh` units so\nthe content below stays still. A container that grows token by token drags\nthe whole page, which is worse for someone using magnification than the wait.\n\nThe cursor is CSS and `aria-hidden`. It stops blinking under\n`prefers-reduced-motion` — a blinking element is a WCAG 2.3.1 concern and a\ngenuine problem for some vestibular and attention conditions.",
  "import": "import { StreamingText } from 'ionbase-ui';",
  "status": "stable",
  "summary": "Model output arriving a token at a time, in a container that does not move. Deliberately not a live region.",
  "useWhen": [
    "rendering a response that streams in rather than arriving whole",
    "the reader should be able to start reading before generation finishes"
  ],
  "useInstead": [
    {
      "when": "the text is already complete",
      "use": "Alert",
      "why": "nothing about this component helps once there is nothing left to arrive"
    },
    {
      "when": "you are showing progress through steps rather than producing text",
      "use": "AgentActivity"
    }
  ],
  "a11y": {
    "role": "region when `label` is set, none otherwise",
    "guarantees": [
      "`aria-live=\"off\"`, deliberately — a polite live region re-announces the whole answer on every token, which makes a streaming response unusable with a screen reader",
      "`aria-busy` while streaming, so assistive tech knows the region is unsettled",
      "the text stays ordinary readable content throughout, so a screen-reader user can read ahead exactly as a sighted user does",
      "the cursor is `aria-hidden`, is a styled box rather than a text character, and stops blinking under `prefers-reduced-motion` (WCAG 2.3.1)"
    ],
    "requires": [
      "announcing completion yourself, with your own `role=\"status\"`, if the surface wants it — a chat with ten turns on screen does not want ten announcements",
      "`label` when the region needs a name of its own"
    ]
  },
  "antiPatterns": [
    {
      "dont": "wrapping it in `aria-live=\"polite\"`",
      "why": "every token queues an announcement; the user hears the answer stuttered dozens of times and cannot get ahead of it"
    },
    {
      "dont": "letting the container grow token by token with no reserved height",
      "do": "`minLines`",
      "why": "the content below climbs the screen while someone is reading it, which is worse under magnification than the wait"
    },
    {
      "dont": "a text-character cursor like ▌",
      "why": "some screen readers read it aloud even inside an aria-hidden span"
    }
  ],
  "stylesheet": "src/styles/streaming-text.css",
  "tokens": [
    "--font-family-sans",
    "--ion-streaming-min-lines",
    "--text-default",
    "--text-tertiary",
    "--type-body",
    "--type-body-line-height"
  ],
  "props": {
    "children": {
      "type": "React.ReactNode",
      "required": false,
      "origin": "own",
      "description": "The text so far. Re-render with more of it; this component appends nothing."
    },
    "isStreaming": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own",
      "description": "More is still arriving. Shows the cursor and marks the region busy."
    },
    "minLines": {
      "type": "number | undefined",
      "required": false,
      "origin": "own",
      "description": "Rows of height to hold while the text is short, so the page below does not\nclimb the screen as tokens arrive. Costs blank space at the start and buys\na layout that does not move under a reader."
    },
    "label": {
      "type": "string | undefined",
      "required": false,
      "origin": "own",
      "description": "Accessible name for the region."
    },
    "hideCursor": {
      "type": "boolean | undefined",
      "required": false,
      "origin": "own",
      "description": "Hide the trailing cursor. The text still marks itself busy."
    }
  },
  "propCounts": {
    "own": 5,
    "aria": 0,
    "dom": 277,
    "other": 0
  }
}
