# Aihio Prompt Fragment

Use this fragment when you want an AI system to generate Aihio UI quickly and correctly. It is intentionally biased toward safe, schema-backed markup over novelty.

## Strategy

- Match user intent to components before thinking about visual styling.
- Start from a seeded pattern when the request already resembles auth, settings, empty states, destructive confirmations, tabbed settings, inline validation, toast alerts, or a table of records.
- Use native HTML for document semantics around Aihio custom elements.
- When markup needs CSS of its own, use the semantic tokens below (`var(--aihio-color-muted-fg)`, `var(--aihio-spacing-stack-md)`), never raw colours or pixel values. `data-aihio-intent` annotations say what an element is for.
- Adapt existing variants, sizes, slots, and compositions instead of inventing new APIs.

## Authoring Rules

- Only use documented Aihio tags, related subcomponents, attributes, and pattern compositions.
- Keep custom-element trees shallow and follow required slots, children, and parent relationships.
- Treat boolean attributes as presence or absence, never stringified booleans.
- A call to action that goes to another page is a link drawn as a button: `<aihio-button><a href="/pricing">See pricing</a></aihio-button>`. Never navigate from a click handler, and never put `href` on `aihio-button`.
- A table of records is `<aihio-table>` around a native `<table>`: a `<caption>` (or `aria-labelledby` pointing at the heading above it), `<th scope="col">` in a `<thead>`, and `<th scope="row">` for the cell that names each row. Make a column sortable with `data-sortable` on its `<th>`, never with click handlers on headers, cells, or rows; link the row's name instead of making the row clickable. For more rows than a page can hold (thousands and up), show one page with `aihio-pagination` and `manual-sort`; use `aihio-data-grid`, which renders only the rows in view, when the list has to be one continuous scroll.
- Open and close overlays from markup: give the overlay an `id`, and put `commandfor="<id>"` plus one of its declared commands (such as `command="--open"`) on the `aihio-button` that controls it. Never use built-in commands like `show-modal` on an Aihio component.
- When the request is ambiguous, choose the simplest accessible composition that satisfies the intent.
- If a seeded pattern already fits, adapt that pattern instead of freehanding the structure.

## Component Inventory

Use top-level components first, then reach for related subcomponents only inside their intended parent patterns.

### Top-Level Components

- `aihio-alert` - A callout for a message the user should notice where it is shown: a failed save, a confirmation that something worked, a warning about a setting. Only the destructive variant interrupts a screen reader.
  Intents: `alert`, `informational`, `status`.
  Attributes: `variant=default|success|warning|destructive`.
  Slots: `default`, `title`, `description`.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-avatar` - A round picture of a person or entity, such as the account owner in a header, falling back to initials when there is no image or it fails to load.
  Intents: `identity`, `labeling`.
  Attributes: `src=string`, `alt=string`, `fallback=string`, `size=sm|md|lg`.
  Slots: none.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-badge` - A small non-interactive label for a status, count, or category, such as Live, Beta, or 3 new.
  Intents: `status`, `labeling`, `metadata`.
  Attributes: `variant=default|secondary|outline|success|warning|destructive`.
  Slots: `default`.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-button` - An action the user takes: submit a form, open a dialog, run a command. Renders a real <button>; variant="default" is the primary style and variant="destructive" marks an irreversible action. To go to another page, put an <a href> inside it instead: the link becomes the control, styled as the button.
  Intents: `action`, `primary-action`, `secondary-action`, `destructive-action`, `navigation`.
  Attributes: `variant=default|secondary|outline|ghost|link|destructive`, `size=sm|md|lg|icon`, `disabled`, `loading`, `type=button|submit|reset`, `name=string`, `value=string`, `form=string`, `formaction=string`, `formmethod=get|post|dialog`, `formenctype=application/x-www-form-urlencoded|multipart/form-data|text/plain`, `formnovalidate`, `formtarget=string`, `command=string`, `commandfor=string`.
  Slots: `default`.
  Methods: `click()`, `focus()`, `blur()`.
  Requires: none.
  Related: none.

- `aihio-card` - A bordered surface that groups related content, with an optional header (title and description), content area, and footer for actions, such as a settings section or an item in a grid.
  Intents: `container`, `surface`.
  Attributes: `variant=default|outline`.
  Slots: `default`.
  Methods: none.
  Requires: none.
  Related: `aihio-card-header`, `aihio-card-title`, `aihio-card-description`, `aihio-card-content`, `aihio-card-footer`.

- `aihio-cluster` - Horizontal layout primitive for grouped inline items such as button rows, badge lists, and toolbars. Wraps when space runs out.
  Intents: `layout`, `container`.
  Attributes: `gap=tight|sm|md|lg`, `align=start|center|end|baseline`, `justify=start|center|end|between`, `grow`.
  Slots: `default`.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-combobox` - A text field for picking one option from a long list: typing filters the options, and the chosen option's value submits with the form. Use it when there are too many options to scan comfortably (roughly more than ten), or when the options come from a search.
  Intents: `form-field`, `selection`, `text-entry`.
  Attributes: `value=string`, `name=string`, `form=string`, `placeholder=string`, `disabled`, `readonly`, `required`, `error`, `size=sm|md|lg`, `filter=contains|starts-with|none`, `allow-custom`, `loading`, `empty-text=string`, `loading-text=string`, `results-text=string`, `open`.
  Slots: none.
  Methods: `open()`, `close()`, `toggle()`, `focus()`, `checkValidity()`, `reportValidity()`, `setCustomValidity()`.
  Requires: none.
  Related: `aihio-option`.

- `aihio-data-grid` - A virtualized data table for large datasets, more rows than a page can hold (thousands to hundreds of thousands): one scrolling box in which only the rows in view are rendered, by your code, when the grid asks for them. Arrow keys move between cells as in a spreadsheet, and the grid tells assistive technology how many rows there are and which ones it shows. Find in page, printing, and reading the rows in a screen reader's browse mode only reach the rendered rows; when people need those, use aihio-table with aihio-pagination instead.
  Intents: `tabular-data`.
  Attributes: `row-count=number`, `density=default|compact`, `loading`, `sort-ascending-text=string`, `sort-descending-text=string`.
  Native elements: `<th>` (`data-sortable=string`, `aria-sort=ascending|descending|none|other`, `data-numeric`), `<td>` (`data-numeric`).
  Slots: none.
  Methods: `scrollToRow()`.
  Requires: children `table`.
  Related: none.

- `aihio-dialog` - A modal dialog for a focused task or a confirmation before an irreversible action, such as deleting a project. Opens and closes from markup: commandfor plus command="--open" or command="--close".
  Intents: `overlay`, `modal`, `dismissible`.
  Attributes: `open`.
  Slots: none.
  Methods: `open()`, `close()`.
  Commands: `--open` (Opens the dialog modally. Focus returns to the invoking button when it closes), `--close` (Requests that the dialog close, with reason "command"; aihio-before-close can still cancel it), `--toggle` (Opens the dialog when it is closed and closes it when it is open).
  Requires: none.
  Related: `aihio-dialog-header`, `aihio-dialog-title`, `aihio-dialog-description`, `aihio-dialog-footer`.

- `aihio-dropdown` - A menu of actions or links opened from a trigger button, such as an account menu or a row's More actions. Not for choosing a form value: use aihio-combobox for that.
  Intents: `menu`, `overlay`, `dismissible`.
  Attributes: `open`, `align=start|end`.
  Slots: `trigger`, `default`.
  Methods: `open()`, `close()`, `toggle()`.
  Requires: slots `trigger`.
  Related: `aihio-dropdown-item`, `aihio-dropdown-separator`.

- `aihio-field` - Form field wrapper that lays out a label, control, description, and error message, and wires the accessibility relationships between them.
  Intents: `form-field`, `layout`, `labeling`.
  Attributes: `error`.
  Slots: `label`, `default`, `description`, `error`.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-grid` - Layout primitive for a grid of equal columns, such as a grid of cards. Columns are never narrower than 16rem and drop away as the grid narrows, so it needs no breakpoints; columns caps how many there are.
  Intents: `layout`, `container`.
  Attributes: `columns=2|3|4`, `gap=tight|sm|md|lg`.
  Slots: `default`.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-input` - A single-line text field (text, email, password, number, search, and the other native types) that submits with its form. Put it inside aihio-field to get a label, description, and error message.
  Intents: `form-field`, `text-entry`.
  Attributes: `type=string`, `size=sm|md|lg`, `placeholder=string`, `disabled`, `error`, `value=string`, `name=string`, `required`, `readonly`, `autocomplete=string`, `min=string`, `max=string`, `minlength=number`, `maxlength=number`, `pattern=string`, `step=string`, `inputmode=string`, `enterkeyhint=string`, `autocapitalize=string`, `spellcheck`, `multiple`, `accept=string`, `capture=string`, `list=string`, `form=string`.
  Slots: none.
  Methods: `checkValidity()`, `reportValidity()`, `setCustomValidity()`, `select()`, `focus()`.
  Requires: none.
  Related: none.

- `aihio-pagination` - Navigation between the pages of a long list (a pager): previous and next, the first and last page, and the pages around the current one. With href each page is a link with its own address; without it each page is a button your code answers. Use it under a table, search results, or a grid of cards when the whole list is too long to show at once.
  Intents: `navigation`.
  Attributes: `page=number`, `pages=number`, `href=string`, `previous-text=string`, `next-text=string`, `page-text=string`.
  Slots: none.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-stack` - Vertical layout primitive that applies the system's spacing rhythm between its children.
  Intents: `layout`, `container`.
  Attributes: `gap=tight|sm|md|lg`, `align=start|center|end|stretch`.
  Slots: `default`.
  Methods: none.
  Requires: none.
  Related: none.

- `aihio-switch` - An on/off setting that submits with its form, such as Email notifications on a settings page. Renders a real <input type="checkbox" role="switch">. Put it inside aihio-field for a label and description; for a pressed state in a toolbar (Bold, Italic) use aihio-toggle instead.
  Intents: `form-field`, `toggle-state`.
  Attributes: `checked`, `disabled`, `required`, `name=string`, `value=string`, `form=string`.
  Slots: none.
  Methods: `click()`, `focus()`, `checkValidity()`, `reportValidity()`, `setCustomValidity()`.
  Requires: none.
  Related: none.

- `aihio-table` - A data table: records in rows and columns, compared by scanning down a column. It wraps a native <table> and gives it the system's styles, sortable columns, and a scroll box the keyboard can reach when the table is too wide for the screen. Use it for records people compare (invoices, members, deployments); use aihio-grid of cards for a few summaries, and never a table for layout. For more rows than a page can hold, show one page at a time with aihio-pagination, or use aihio-data-grid.
  Intents: `tabular-data`.
  Attributes: `density=default|compact`, `sticky-header`, `manual-sort`, `loading`, `sort-ascending-text=string`, `sort-descending-text=string`.
  Native elements: `<th>` (`data-sortable=string`, `aria-sort=ascending|descending|none|other`, `data-numeric`, `data-sort-value=string`), `<td>` (`data-sort-value=string`, `data-numeric`).
  Slots: none.
  Methods: `sort()`.
  Requires: children `table`.
  Related: none.

- `aihio-tabs` - Switches between sibling panels of content with a row of tabs, such as the sections of a settings page. Arrow keys move between tabs.
  Intents: `tabs`, `navigation`, `layout`.
  Attributes: `value=string`.
  Slots: none.
  Methods: none.
  Requires: children `aihio-tab-list`, `aihio-tab-panel`.
  Related: `aihio-tab-list`, `aihio-tab`, `aihio-tab-panel`.

- `aihio-toggle` - A button that stays pressed or unpressed, such as Bold in a text toolbar. It renders a real <button aria-pressed>.
  Intents: `toggle-state`, `action`.
  Attributes: `pressed`, `disabled`, `variant=default|outline`, `size=sm|md|lg`.
  Slots: `default`.
  Methods: `click()`, `focus()`, `blur()`.
  Requires: none.
  Related: none.

### Related Subcomponents

- `aihio-card-header` - Card header container
  Parent: `aihio-card`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-card-title` - Card title text
  Parent: `aihio-card`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-card-description` - Card description text
  Parent: `aihio-card`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-card-content` - Card main content area
  Parent: `aihio-card`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-card-footer` - Card footer with actions
  Parent: `aihio-card`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-option` - One choice in an aihio-combobox. Its text (or label attribute) is what typing matches and what the field shows once chosen.
  Parent: `aihio-combobox`.
  Attributes: `value=string`, `label=string`, `disabled`.
  Events: none.
  Methods: none.

- `aihio-dialog-header` - Dialog header container
  Parent: `aihio-dialog`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-dialog-title` - Dialog title text
  Parent: `aihio-dialog`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-dialog-description` - Dialog description text
  Parent: `aihio-dialog`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-dialog-footer` - Dialog footer with actions
  Parent: `aihio-dialog`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-dropdown-item` - A selectable item in the dropdown. Give it a single <a href> child to make it a link.
  Parent: `aihio-dropdown`.
  Attributes: `value=string`, `disabled`.
  Events: `aihio-select`.
  Methods: none.

- `aihio-dropdown-separator` - A visual separator between dropdown items
  Parent: `aihio-dropdown`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-tab-list` - Container for tab triggers
  Parent: `aihio-tabs`.
  Attributes: none.
  Events: none.
  Methods: none.

- `aihio-tab` - Individual tab trigger
  Parent: `aihio-tabs`.
  Attributes: `value=string`, `disabled`.
  Events: `aihio-tab-select`.
  Methods: none.

- `aihio-tab-panel` - Content panel for a tab
  Parent: `aihio-tabs`.
  Attributes: `value=string`.
  Events: none.
  Methods: none.

## Intent to Component Map

Map requested meaning to these schema-backed components before thinking about visual treatment.

- `action` - Any user-triggerable action.
  Components: `aihio-button`, `aihio-toggle`.

- `primary-action` - The dominant action in a given context (e.g. submit, confirm).
  Components: `aihio-button`.

- `secondary-action` - A supporting action (e.g. cancel, reset).
  Components: `aihio-button`.

- `destructive-action` - An action with irreversible or data-losing consequences.
  Components: `aihio-button`.

- `toggle-state` - Toggles a binary state without navigating or submitting.
  Components: `aihio-switch`, `aihio-toggle`.

- `navigation` - Moves the user to a different view, section, or URL.
  Components: `aihio-button`, `aihio-pagination`, `aihio-tabs`.

- `selection` - Lets the user pick one item from a set.
  Components: `aihio-combobox`.

- `form-field` - Collects a single value from the user as part of a form.
  Components: `aihio-combobox`, `aihio-field`, `aihio-input`, `aihio-switch`.

- `text-entry` - A form field specifically for free-form text.
  Components: `aihio-combobox`, `aihio-input`.

- `status` - Displays current state (badges, pills, counts).
  Components: `aihio-alert`, `aihio-badge`.

- `informational` - Non-blocking guidance or context.
  Components: `aihio-alert`.

- `alert` - Important message that requires attention.
  Components: `aihio-alert`.

- `error` - Communicates a failure or invalid state.
  Components: no direct top-level component; resolve through composition.

- `container` - Groups related content without imposing meaning.
  Components: `aihio-card`, `aihio-cluster`, `aihio-grid`, `aihio-stack`.

- `surface` - A raised or bordered region that frames content (card, panel).
  Components: `aihio-card`.

- `layout` - Arranges other components spatially (tab list, grid).
  Components: `aihio-cluster`, `aihio-field`, `aihio-grid`, `aihio-stack`, `aihio-tabs`.

- `header` - A titled heading region inside a surface.
  Components: no direct top-level component; resolve through composition.

- `footer` - An actions or summary region inside a surface.
  Components: no direct top-level component; resolve through composition.

- `overlay` - Content that renders on top of the page (dialog, popover).
  Components: `aihio-dialog`, `aihio-dropdown`.

- `modal` - An overlay that blocks interaction with the rest of the page.
  Components: `aihio-dialog`.

- `menu` - A list of actions triggered from an anchor element.
  Components: `aihio-dropdown`.

- `identity` - Represents a person or entity (avatar, name chip).
  Components: `aihio-avatar`.

- `labeling` - Short label or tag attached to other content.
  Components: `aihio-avatar`, `aihio-badge`, `aihio-field`.

- `metadata` - Ancillary descriptive text (titles, descriptions, captions).
  Components: `aihio-badge`.

- `tabs` - Switches between sibling panels using a row of triggers.
  Components: `aihio-tabs`.

- `tabular-data` - Records laid out in rows and columns, compared by scanning down a column (a data table).
  Components: `aihio-data-grid`, `aihio-table`.

- `dismissible` - Can be closed or hidden by the user.
  Components: `aihio-dialog`, `aihio-dropdown`.

## Pattern Inventory

Start from these canonical compositions when the request already matches one of them.

- `auth-form` - Card-based authentication form with labelled fields and a dominant submit action inside the form element.
  Intents: `surface`, `layout`, `form-field`, `text-entry`, `primary-action`, `secondary-action`.
  Required components: `aihio-card`, `aihio-field`, `aihio-input`, `aihio-cluster`, `aihio-button`.
  Variations: `passwordless` (Email-only sign-in flow that requests a magic link.), `signup` (Account-creation variant with name, email, and password confirmation.).

- `data-card-grid` - A dashboard-style grid of compact summary cards with status, ownership, and a follow-up action. aihio-grid drops columns as it narrows, and the cards are a list.
  Intents: `layout`, `surface`, `status`, `identity`, `secondary-action`.
  Required components: `aihio-grid`, `aihio-card`, `aihio-badge`, `aihio-avatar`, `aihio-button`, `aihio-stack`, `aihio-cluster`.
  Variations: none.

- `data-table` - A list of records to compare and sort: a heading with the primary action, a search form, a table whose columns sort, whose row headers link to each record, and whose rows carry a status and a menu of actions, and aihio-pagination by link. The table is named by the section's heading, and on a narrow screen it scrolls inside its own box.
  Intents: `tabular-data`, `status`, `menu`, `primary-action`, `navigation`, `form-field`, `layout`.
  Required components: `aihio-table`, `aihio-button`, `aihio-field`, `aihio-input`, `aihio-stack`, `aihio-cluster`.
  Variations: `no-results` (A search that matched nothing keeps the column headers, says so in a row spanning them, and links back to every record.).

- `destructive-confirmation` - A modal confirmation flow for irreversible actions with cancel-first ordering. The trigger opens the dialog and Cancel closes it through Invoker Commands, and the confirm button submits the dialog's form, so the flow works without script.
  Intents: `overlay`, `modal`, `destructive-action`, `secondary-action`, `dismissible`.
  Required components: `aihio-dialog`, `aihio-button`.
  Variations: none.

- `empty-state` - A blank-slate surface that explains what is missing and points the user toward the next action.
  Intents: `surface`, `informational`, `primary-action`, `secondary-action`.
  Required components: `aihio-card`, `aihio-button`.
  Variations: `search-results` (No-results variant that suggests clearing filters before creating a new item.).

- `inline-form-validation` - A compact form that keeps the invalid field, its message, and the corrective action in one place.
  Intents: `layout`, `form-field`, `text-entry`, `alert`, `primary-action`, `secondary-action`.
  Required components: `aihio-stack`, `aihio-field`, `aihio-input`, `aihio-alert`, `aihio-cluster`, `aihio-button`.
  Variations: `success` (Resolved state after the invalid field has been corrected.).

- `settings-section` - A settings panel in a card: a form of labelled fields — text, a native select for a short list of choices, and a switch for an on/off setting — with Reset and Save that reset and submit it.
  Intents: `surface`, `form-field`, `text-entry`, `toggle-state`, `status`, `primary-action`, `secondary-action`, `layout`.
  Required components: `aihio-card`, `aihio-field`, `aihio-input`, `aihio-switch`, `aihio-button`, `aihio-badge`, `aihio-stack`, `aihio-cluster`.
  Variations: none.

- `tabbed-settings` - A settings view split across tabs so related configuration stays grouped without overwhelming the page. Each panel is its own form with its own Save.
  Intents: `tabs`, `navigation`, `layout`, `surface`, `form-field`, `text-entry`, `toggle-state`, `primary-action`.
  Required components: `aihio-tabs`, `aihio-card`, `aihio-field`, `aihio-input`, `aihio-switch`, `aihio-button`, `aihio-stack`, `aihio-cluster`.
  Variations: none.

- `toast-alert-stack` - A lightweight stacked-notification pattern for transient system updates.
  Intents: `alert`, `informational`, `status`, `layout`.
  Required components: `aihio-alert`, `aihio-stack`.
  Variations: none.

## Accessibility Obligations

These are author responsibilities the components do not infer for you automatically.

- `aihio-alert` (warn) - when variant="destructive", Include slot="title" or slot="description" so assistive tech has content to announce. Colour alone is not a sufficient signal.
- `aihio-alert` (error) - when role is set on the alert to anything but its variant's own (alert for destructive, status otherwise), Remove the role and let the variant set it: role="alert" for destructive, which interrupts the screen reader, and role="status" for the rest, which waits its turn.
- `aihio-avatar` (error) - when src is set, Provide alt describing the person or entity (e.g. alt="Jane Doe"). Empty alt is only acceptable for purely decorative avatars.
- `aihio-avatar` (error) - when src is not set and fallback is empty, Provide alt so initials can be derived, or set fallback explicitly.
- `aihio-badge` (warn) - when badge conveys information not present elsewhere (e.g. unread count), Include the meaning in visible text or an aria-label on the surrounding context. Badges have no inherent role.
- `aihio-button` (error) - when size="icon" or the button has no visible text, Provide aria-label describing the action (e.g. aria-label="Close").
- `aihio-button` (warn) - when the button submits a form, Set type="submit" and either place the button inside its <form> or reference that form with the form attribute.
- `aihio-button` (error) - when the button goes to another page from a click handler (onclick sets location, or calls window.open or router.push), Put an <a href> inside aihio-button instead: <aihio-button><a href="/pricing">See pricing</a></aihio-button>. A button that navigates is announced as a button, shows no URL, cannot be opened in a new tab, and does nothing without script.
- `aihio-button` (error) - when the button wraps an <a>, Give the <a> an href. Without one it is not a link: it has no role, takes no focus, and goes nowhere.
- `aihio-button` (warn) - when the button wraps an <a>, Leave type, name, value, form*, command, and commandfor off the host. They configure a <button>, so on a link they do nothing.
- `aihio-card` (warn) - when card acts as a link or button (entire surface is clickable, or it has a click handler), Wrap the card in an <a> or attach role="button" + keyboard handlers on the outer element. aihio-card itself has no interactive role.
- `aihio-cluster` (warn) - when the cluster is a toolbar of related controls, Add role="toolbar" and an aria-label on the cluster; it is presentational by default.
- `aihio-combobox` (error) - when the combobox is not inside an aihio-field, Label it with aria-labelledby pointing at visible text, or aria-label. Placeholder is not a label, and a wrapping <label> would fold the option text into the field's name.
- `aihio-combobox` (error) - when two aihio-option children share a value, Give every option a distinct value; the value is how the chosen option is found again.
- `aihio-combobox` (warn) - when the combobox is inside a <form> and its value should be submitted, Set name. A combobox without a name is omitted from FormData.
- `aihio-data-grid` (error) - when row-count is missing or not a whole number, Set row-count to how many rows there are in all. The grid asks for rows by number, and sizes its scroll box from the count.
- `aihio-data-grid` (warn) - when row-count is above 350,000, Narrow the rows with a filter, or show them a page at a time with aihio-table and aihio-pagination. Firefox stops a box's height at 17.9 million pixels, about 389,000 rows of the default height, and rows past it cannot be scrolled to.
- `aihio-data-grid` (error) - when the table has no caption, aria-label, or aria-labelledby, Name the table: a <caption>, aria-labelledby pointing at the visible heading above it, or aria-label.
- `aihio-data-grid` (error) - when the table has no <th> cells, Name each column with a <th> in the <thead> row.
- `aihio-data-grid` (warn) - when a sortable header has no column name, Give each sortable header a name, data-sortable="duration", so aihio-sort says which column to sort by without depending on the header's wording.
- `aihio-data-grid` (warn) - when people need to find text in the page, print every row, or read the rows in a screen reader's browse mode, Use aihio-table with aihio-pagination instead. A grid has only the rows in view in the page, and those are all that find in page, printing, and browse mode reach.
- `aihio-dialog` (error) - when dialog has no aihio-dialog-title, Provide aria-label on aihio-dialog describing the dialog's purpose; otherwise screen readers announce an unnamed dialog.
- `aihio-dialog` (warn) - when dialog confirms a destructive action, Label the confirming button with variant="destructive" and keep Cancel as the first focusable control.
- `aihio-dropdown` (error) - when the trigger is icon-only, Provide aria-label on the trigger (e.g. aria-label="Open menu").
- `aihio-field` (error) - when the field has no slot="label" content, Provide slot="label" content, or an aria-label on the control. aihio-field wires a label but cannot invent one.
- `aihio-field` (error) - when the error attribute is written on the field, Write the message in slot="error" and leave the attribute to the field, which sets it from that content. An error attribute with no message marks the field invalid with nothing to announce.
- `aihio-grid` (warn) - when the grid is a list of like items, such as project cards, Give it list semantics when the count matters to the reader: role="list" on aihio-grid and role="listitem" on each item.
- `aihio-input` (error) - when input has no visible <label> associated by for/id, Provide aria-label on aihio-input, or wrap in a <label>. Placeholder is not a label.
- `aihio-input` (error) - when error=true, Describe the error via aria-describedby pointing to a visible message; the red border alone is not conveyed to screen readers.
- `aihio-input` (warn) - when the field is inside a <form> and its value should be submitted, Set name. A field without a name is omitted from FormData entirely.
- `aihio-pagination` (error) - when pages is missing or not a whole number, or page is outside 1 to pages, Set pages to how many pages there are, and page to the current one, counting from 1.
- `aihio-pagination` (error) - when href is set without {page}, Put {page} where the page number goes in href. Without it every page links to the same address.
- `aihio-pagination` (warn) - when more than one aihio-pagination is on the page, Name each one after the list it pages through, aria-label="Invoice pages", so the landmarks can be told apart.
- `aihio-stack` (warn) - when the stack groups a titled region of the page, Use a semantic sectioning element (<section>, <nav>, <main>) around or inside the stack. aihio-stack does not create a landmark.
- `aihio-switch` (error) - when the switch has no label, Name the switch after the setting it controls: put it in aihio-field with a <label slot="label">, wrap it in a <label>, or give it aria-label. Never label it with its state ("On", "Enabled").
- `aihio-switch` (warn) - when the switch is inside a <form> and its state should be submitted, Set name. A switch without a name is omitted from FormData entirely.
- `aihio-switch` (warn) - when the switch is named by a state word (On, Off, Enabled), Name the switch after the setting it controls. The switch announces its own state, so a switch named "On" is read as "On, switch, on" and never says what is on.
- `aihio-table` (error) - when the table has no caption, aria-label, or aria-labelledby, Name the table: a <caption> as its first child, aria-labelledby pointing at the visible heading above it, or aria-label. An unnamed table is announced only as "table", and so is the scroll box it gets on a narrow screen.
- `aihio-table` (error) - when the table has no <th> cells, Name each column with a <th> in a <thead> row, and each row with <th scope="row"> where one cell identifies it. Bold text in an ordinary cell is not a header.
- `aihio-table` (error) - when a row, header, or cell responds to clicks and holds no link or button, Sort with data-sortable on the <th>, and put a link or button in the row for anything else, such as a link in the cell that names it. A click handler on a row or cell is reachable by pointer only.
- `aihio-table` (warn) - when manual-sort is set and a sortable header has no column name, Give each sortable header a name, data-sortable="amount", so aihio-sort says which column to sort by without depending on the header's wording.
- `aihio-tabs` (error) - when every aihio-tab and aihio-tab-panel, Match each tab's value to exactly one panel's value. Mismatched values leave panels orphaned.
- `aihio-toggle` (error) - when toggle has no visible text (icon-only), Provide aria-label describing what the toggle controls (e.g. aria-label="Bold").
- `aihio-toggle` (warn) - when the toggle turns a setting on or off, or is named by a state word (On, Enabled), Name the toggle after what it controls (e.g. "Bold"), never after its state: a toggle labelled "Enabled" is announced as "Enabled, toggle button, pressed" and never says what is enabled. For an on/off setting in a form, use aihio-switch.

## Hard Rules from Counterexamples

Do not invent unsupported APIs or invalid compositions. Treat these schema-backed mistakes as hard failures.

- `aihio-alert` - avoid `<aihio-alert variant="error">Oops</aihio-alert>` (aihio lint: invalid-enum-attribute) - variant="error" is not valid. Use variant="destructive" for error states.
  Instead: `<aihio-alert variant="destructive"> <div slot="title">Could not save</div> </aihio-alert>`
- `aihio-alert` - avoid `<aihio-alert><aihio-button>OK</aihio-button></aihio-alert>` (aihio lint: forbidden-descendant) - Alerts are non-interactive surfaces. For acknowledgeable prompts use aihio-dialog.
  Instead: `<aihio-alert> <div slot="title">Your changes were saved</div> </aihio-alert>`
- `aihio-alert` - avoid `<aihio-alert variant="success" role="alert">Saved</aihio-alert>` (aihio lint: alert-role) - role="alert" interrupts the screen reader. A confirmation should announce politely — drop the role and let the success variant set role="status".
  Instead: `<aihio-alert variant="success">Saved</aihio-alert>`
- `aihio-avatar` - avoid `<aihio-avatar src="/u.jpg"></aihio-avatar>` (aihio lint: avatar-alt) - Missing alt. Screen readers will announce only the filename.
  Instead: `<aihio-avatar src="/u.jpg" alt="Jane Doe"></aihio-avatar>`
- `aihio-avatar` - avoid `<aihio-avatar>JD</aihio-avatar>` (aihio lint: invalid-child) - Avatar renders its own content; passed children are replaced. Use the fallback attribute instead.
  Instead: `<aihio-avatar fallback="JD" alt="Jane Doe"></aihio-avatar>`
- `aihio-badge` - avoid `<aihio-badge><aihio-button>Delete</aihio-button></aihio-badge>` (aihio lint: forbidden-descendant) - Badges are non-interactive labels. Put interactive elements outside the badge.
  Instead: `<aihio-cluster gap="tight"> <aihio-badge>Draft</aihio-badge> <aihio-button variant="ghost" size="sm">Delete</aihio-button> </aihio-cluster>`
- `aihio-badge` - avoid `<aihio-badge variant="primary">New</aihio-badge>` (aihio lint: invalid-enum-attribute) - variant="primary" is not valid. Use variant="default" for the primary style.
  Instead: `<aihio-badge variant="default">New</aihio-badge>`
- `aihio-button` - avoid `<aihio-button variant="primary">Save</aihio-button>` (aihio lint: invalid-enum-attribute) - variant="primary" is not valid. The primary style is variant="default".
  Instead: `<aihio-button variant="default">Save</aihio-button>`
- `aihio-button` - avoid `<aihio-button><aihio-button>Save</aihio-button></aihio-button>` (aihio lint: forbidden-descendant) - Buttons must not be nested. Use sibling buttons or aihio-dropdown for grouped actions.
  Instead: `<aihio-cluster> <aihio-button variant="outline">Cancel</aihio-button> <aihio-button>Save</aihio-button> </aihio-cluster>`
- `aihio-button` - avoid `<aihio-button size="icon">&#x2715;</aihio-button>` (aihio lint: button-accessible-name) - Icon-only button is missing an accessible name. Add aria-label="Close" (or similar).
  Instead: `<aihio-button size="icon" aria-label="Close">&#x2715;</aihio-button>`
- `aihio-button` - avoid `<form> <aihio-input name="email" aria-label="Email"></aihio-input> </form> <aihio-button type="submit">Sign in</aihio-button>` (aihio lint: button-form-owner) - A submit button outside the <form> never submits it. Move the button inside the form element.
  Instead: `<form> <aihio-input name="email" aria-label="Email"></aihio-input> <aihio-button type="submit">Sign in</aihio-button> </form>`
- `aihio-button` - avoid `<aihio-button onclick="location.href='/pricing'">See pricing</aihio-button>` (aihio lint: button-link-navigation) - A button that navigates is announced as a button, shows no URL, cannot be opened in a new tab, and does nothing without script. Put a link inside it: <aihio-button><a href="/pricing">See pricing</a></aihio-button>.
  Instead: `<aihio-button><a href="/pricing">See pricing</a></aihio-button>`
- `aihio-button` - avoid `<aihio-button href="/pricing">See pricing</aihio-button>` (aihio lint: unknown-attribute) - aihio-button has no href; nothing reads it. Put the link inside: <aihio-button><a href="/pricing">See pricing</a></aihio-button>.
  Instead: `<aihio-button><a href="/pricing">See pricing</a></aihio-button>`
- `aihio-card` - avoid `<aihio-card><aihio-card-title>Hi</aihio-card-title></aihio-card>` (aihio lint: invalid-child) - Title must live inside aihio-card-header, not directly inside aihio-card.
  Instead: `<aihio-card> <aihio-card-header> <aihio-card-title>Hi</aihio-card-title> </aihio-card-header> </aihio-card>`
- `aihio-card` - avoid `<aihio-card onclick="openDetails()"><p>Open details</p></aihio-card>` (aihio lint: card-click-handler) - Clickable cards need keyboard-accessible semantics. Wrap the card in a link, or add role="button" plus keyboard handlers to the interactive surface.
  Instead: `<a href="/details"> <aihio-card><p>Open details</p></aihio-card> </a>`
- `aihio-cluster` - avoid `<aihio-cluster justify="space-between"><aihio-button>Save</aihio-button></aihio-cluster>` (aihio lint: invalid-enum-attribute) - justify="space-between" is not valid. Use justify="between".
  Instead: `<aihio-cluster justify="between"><aihio-button>Save</aihio-button></aihio-cluster>`
- `aihio-cluster` - avoid `<aihio-stack><aihio-button>Cancel</aihio-button><aihio-button>Save</aihio-button></aihio-stack>` - A row of actions belongs in aihio-cluster. aihio-stack stacks them vertically and stretches them to full width.
  Instead: `<aihio-cluster><aihio-button>Cancel</aihio-button><aihio-button>Save</aihio-button></aihio-cluster>`
- `aihio-cluster` - avoid `<aihio-card-footer> <aihio-cluster justify="end"> <aihio-button>Save</aihio-button> </aihio-cluster> </aihio-card-footer>` (aihio lint: cluster-needs-grow) - justify="end" does nothing here: the cluster is a content-sized flex item inside the footer. Add grow so it fills the row.
  Instead: `<aihio-card-footer> <aihio-cluster grow justify="end"> <aihio-button>Save</aihio-button> </aihio-cluster> </aihio-card-footer>`
- `aihio-combobox` - avoid `<aihio-combobox placeholder="Country"> <aihio-option value="fi">Finland</aihio-option> </aihio-combobox>` (aihio lint: combobox-label) - Placeholder is not a label. Wrap it in aihio-field with a label slot, or add aria-labelledby or aria-label.
  Instead: `<aihio-field> <label slot="label">Country</label> <aihio-combobox name="country"> <aihio-option value="fi">Finland</aihio-option> </aihio-combobox> </aihio-field>`
- `aihio-combobox` - avoid `<label> Country <aihio-combobox> <aihio-option value="fi">Finland</aihio-option> </aihio-combobox> </label>` (aihio lint: combobox-label) - A wrapping <label> takes its name from all of its text, so an open list's options become part of the field's name. Use aihio-field instead.
  Instead: `<aihio-field> <label slot="label">Country</label> <aihio-combobox name="country"> <aihio-option value="fi">Finland</aihio-option> </aihio-combobox> </aihio-field>`
- `aihio-combobox` - avoid `<aihio-combobox aria-label="Size"> <option value="s">Small</option> <option value="m">Medium</option> </aihio-combobox>` (aihio lint: invalid-child) - Native <option> elements are not read. Use aihio-option.
  Instead: `<aihio-combobox aria-label="Size"> <aihio-option value="s">Small</aihio-option> <aihio-option value="m">Medium</aihio-option> </aihio-combobox>`
- `aihio-combobox` - avoid `<aihio-combobox aria-label="Plan"> <aihio-option>Pro</aihio-option> <aihio-option value="Pro">Pro (annual)</aihio-option> </aihio-combobox>` (aihio lint: combobox-option-values) - An option without a value uses its label as its value, so both options have the value "Pro" and the second can never be told apart from the first.
  Instead: `<aihio-combobox aria-label="Plan"> <aihio-option value="pro">Pro</aihio-option> <aihio-option value="pro-annual">Pro (annual)</aihio-option> </aihio-combobox>`
- `aihio-data-grid` - avoid `<aihio-data-grid> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scope="col" data-sortabl...` (aihio lint: data-grid-row-count) - Without row-count the grid does not know how many rows there are, so it asks for none and its scroll box has no height to scroll.
  Instead: `<aihio-data-grid row-count="100000"> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scope="col" data-sortable="method">Method</th> <th scope="col" data-sortable="path">Path</th> <th scope="col" data-sortable="status" data-numeric>Status</th> <th scope="col" data-sortable="duration" data-numeric>Duration (ms)</th> </tr> </thead> <tbody></tbody> </t...`
- `aihio-data-grid` - avoid `<aihio-data-grid row-count="100000"> <table> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scope="col" data-sortable="...` (aihio lint: table-accessible-name) - An unnamed grid is announced only as "grid", which says nothing about what its hundred thousand rows are.
  Instead: `<aihio-data-grid row-count="100000"> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scope="col" data-sortable="method">Method</th> <th scope="col" data-sortable="path">Path</th> <th scope="col" data-sortable="status" data-numeric>Status</th> <th scope="col" data-sortable="duration" data-numeric>Duration (ms)</th> </tr> </thead> <tbody></tbody> </t...`
- `aihio-data-grid` - avoid `<aihio-table row-count="100000"> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scope="co...` (aihio lint: unknown-attribute) - aihio-table renders every row it is given, so it has no row count. A table of more rows than a page can hold is aihio-data-grid, or aihio-table with aihio-pagination.
  Instead: `<aihio-data-grid row-count="100000"> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scope="col" data-sortable="method">Method</th> <th scope="col" data-sortable="path">Path</th> <th scope="col" data-sortable="status" data-numeric>Status</th> <th scope="col" data-sortable="duration" data-numeric>Duration (ms)</th> </tr> </thead> <tbody></tbody> </t...`
- `aihio-data-grid` - avoid `<aihio-data-grid row-count="100000"> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable>Request</th> <th scope="col...` (aihio lint: table-sort-column-name) - Without a column name aihio-sort reports the header's text, which changes when the page is translated.
  Instead: `<aihio-data-grid row-count="100000"> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scope="col" data-sortable="duration">Duration (ms)</th> </tr> </thead> <tbody></tbody> </table> </aihio-data-grid>`
- `aihio-data-grid` - avoid `<aihio-data-grid row-count="2000000"> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id">Request</th> <th scop...` (aihio lint: data-grid-row-limit) - Firefox stops a box's height at 17.9 million pixels, about 389,000 rows of the default height. The scroll ends there, before the rows do, and neither scrolling nor the keyboard reaches the rest.
  Instead: `<aihio-table manual-sort> <table aria-label="Requests"> <thead> <tr> <th scope="col" data-sortable="id" aria-sort="ascending">Request</th> <th scope="col" data-sortable="method">Method</th> <th scope="col" data-sortable="path">Path</th> <th scope="col" data-sortable="status" data-numeric>Status</th> <th scope="col" data-sortable="duration" data-numeric>Duration (ms)</th> </tr> </thead> <tbody> ...`
- `aihio-dialog` - avoid `<aihio-dialog open><p>Delete?</p></aihio-dialog>` (aihio lint: dialog-accessible-name) - Missing header and footer. Provide aihio-dialog-title for a name and aihio-dialog-footer for actions.
  Instead: `<aihio-dialog id="delete-project-dialog"> <aihio-dialog-header> <aihio-dialog-title>Delete project?</aihio-dialog-title> </aihio-dialog-header> <aihio-dialog-footer> <aihio-button commandfor="delete-project-dialog" command="--close" variant="outline">Cancel</aihio-button> <aihio-button variant="destructive">Delete</aihio-button> </aihio-dialog-footer> </aihio-dialog>`
- `aihio-dialog` - avoid `<aihio-dialog><aihio-dialog><p>Nested</p></aihio-dialog></aihio-dialog>` (aihio lint: invalid-child) - Dialogs must not be nested. Close the current dialog before opening another.
  Instead: `<aihio-dialog id="first-step" aria-label="Step 1"><p>Step 1</p></aihio-dialog> <aihio-dialog id="second-step" aria-label="Step 2"><p>Step 2</p></aihio-dialog>`
- `aihio-dialog` - avoid `<aihio-button commandfor="confirm" command="show-modal">Delete</aihio-button> <aihio-dialog id="confirm" aria-label="Confirm delete"></ai...` (aihio lint: invalid-command) - Built-in commands such as show-modal only act on a native <dialog>, and this one is inside the component's shadow root, so nothing happens. Use command="--open".
  Instead: `<aihio-button commandfor="confirm" command="--open">Delete</aihio-button> <aihio-dialog id="confirm" aria-label="Confirm delete"><p>Delete this project?</p></aihio-dialog>`
- `aihio-dropdown` - avoid `<aihio-dropdown> <aihio-dropdown-item>Profile</aihio-dropdown-item> </aihio-dropdown>` (aihio lint: missing-required-slot) - Missing slot="trigger". The dropdown has nothing to open it.
  Instead: `<aihio-dropdown> <aihio-button slot="trigger" variant="outline">Account</aihio-button> <aihio-dropdown-item>Profile</aihio-dropdown-item> </aihio-dropdown>`
- `aihio-dropdown` - avoid `<aihio-dropdown> <aihio-button slot="trigger">Menu</aihio-button> <a href="/x">Link</a> </aihio-dropdown>` (aihio lint: invalid-child) - Children should be aihio-dropdown-item/separator so keyboard navigation and menuitem roles work. Put the link inside an item: <aihio-dropdown-item><a href="/x">Link</a></aihio-dropdown-item>.
  Instead: `<aihio-dropdown> <aihio-button slot="trigger">Menu</aihio-button> <aihio-dropdown-item><a href="/x">Link</a></aihio-dropdown-item> </aihio-dropdown>`
- `aihio-field` - avoid `<aihio-field> <aihio-input name="email"></aihio-input> </aihio-field>` (aihio lint: field-label) - No slot="label", so the control is still unnamed. aihio-field wires a label, it does not invent one.
  Instead: `<aihio-field> <label slot="label">Email</label> <aihio-input name="email"></aihio-input> </aihio-field>`
- `aihio-field` - avoid `<aihio-field error> <span slot="label">Email</span> <aihio-input name="email"></aihio-input> </aihio-field>` (aihio lint: field-error-message) - The error attribute is set by the component from slot="error" content. Writing it by hand with no message leaves nothing to announce.
  Instead: `<aihio-field> <span slot="label">Email</span> <aihio-input name="email"></aihio-input> <span slot="error">Enter an email address.</span> </aihio-field>`
- `aihio-grid` - avoid `<aihio-grid columns="6"><aihio-card></aihio-card></aihio-grid>` (aihio lint: invalid-enum-attribute) - columns takes 2, 3, or 4. Six 16rem columns do not fit a typical content width, and the grid would drop them anyway.
  Instead: `<aihio-grid columns="3"><aihio-card></aihio-card></aihio-grid>`
- `aihio-grid` - avoid `<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">…</div>` (aihio lint: hand-rolled-layout) - A hand-rolled grid ignores the spacing scale and squeezes its columns on narrow screens. Use aihio-grid.
  Instead: `<aihio-grid columns="3">…</aihio-grid>`
- `aihio-input` - avoid `<aihio-input placeholder="Email"></aihio-input>` (aihio lint: input-label) - Placeholder is not a label. Associate a <label> or add aria-label.
  Instead: `<aihio-field> <label slot="label">Email</label> <aihio-input type="email" name="email" placeholder="you@example.com"></aihio-input> </aihio-field>`
- `aihio-input` - avoid `<aihio-input error></aihio-input>` (aihio lint: input-error-description) - error=true without an aria-describedby message leaves screen reader users without context.
  Instead: `<aihio-field> <label slot="label">Email</label> <aihio-input name="email"></aihio-input> <span slot="error">Enter an email address.</span> </aihio-field>`
- `aihio-input` - avoid `<form> <aihio-input aria-label="Email" type="email"></aihio-input> </form>` (aihio lint: input-form-name) - No name attribute, so this field submits nothing. Add name="email".
  Instead: `<form> <aihio-input aria-label="Email" type="email" name="email"></aihio-input> </form>`
- `aihio-pagination` - avoid `<aihio-pagination current="3" total="12" href="/invoices?page={page}"></aihio-pagination>` (aihio lint: unknown-attribute) - The current page is page and the number of pages is pages. Nothing reads current or total, so this shows nothing.
  Instead: `<aihio-pagination page="3" pages="12" href="/invoices?page={page}"></aihio-pagination>`
- `aihio-pagination` - avoid `<aihio-pagination page="3" pages="12" href="/invoices?page="></aihio-pagination>` (aihio lint: pagination-href) - Without {page} in href every page links to the same address.
  Instead: `<aihio-pagination page="3" pages="12" href="/invoices?page={page}"></aihio-pagination>`
- `aihio-pagination` - avoid `<aihio-pagination page="0" pages="12" href="/invoices?page={page}"></aihio-pagination>` (aihio lint: pagination-pages) - Pages count from 1. Page 0 is not a page, so no page is marked current.
  Instead: `<aihio-pagination page="1" pages="12" href="/invoices?page={page}"></aihio-pagination>`
- `aihio-pagination` - avoid `<aihio-pagination page="1" pages="4" href="/invoices?page={page}"></aihio-pagination> <aihio-pagination page="2" pages="9" href="/payment...` (aihio lint: pagination-label) - Two landmarks both named "Pagination" cannot be told apart in a screen reader's list of landmarks. Name each after the list it pages through.
  Instead: `<aihio-pagination page="1" pages="4" href="/invoices?page={page}" aria-label="Invoice pages"></aihio-pagination> <aihio-pagination page="2" pages="9" href="/payments?page={page}" aria-label="Payment pages"></aihio-pagination>`
- `aihio-stack` - avoid `<aihio-stack gap="medium"><p>One</p></aihio-stack>` (aihio lint: invalid-enum-attribute) - gap="medium" is not valid. The scale is tight, sm, md, lg.
  Instead: `<aihio-stack gap="md"><p>One</p></aihio-stack>`
- `aihio-stack` - avoid `<div style="display:flex;flex-direction:column;gap:16px"><p>One</p></div>` (aihio lint: hand-rolled-layout) - Hand-rolled spacing drifts from the token scale. Use aihio-stack so the rhythm stays system-owned.
  Instead: `<aihio-stack gap="md"><p>One</p></aihio-stack>`
- `aihio-switch` - avoid `<span>Email notifications</span> <aihio-switch name="email-notifications"></aihio-switch>` (aihio lint: switch-label) - The visible text is not associated with the switch, so it is announced as an unnamed switch. Use aihio-field with <label slot="label">, or wrap both in a <label>.
  Instead: `<aihio-field> <label slot="label">Email notifications</label> <aihio-switch name="email-notifications"></aihio-switch> </aihio-field>`
- `aihio-switch` - avoid `<aihio-switch aria-label="On" checked></aihio-switch>` (aihio lint: switch-state-name) - A switch named by its state is announced as "On, switch, on". Name it after the setting it controls.
  Instead: `<aihio-switch aria-label="Email notifications" checked></aihio-switch>`
- `aihio-switch` - avoid `<aihio-switch checked="false" aria-label="Weekly summary"></aihio-switch>` (aihio lint: boolean-attribute-value) - Boolean attributes are on by presence. checked="false" starts the switch on; omit the attribute for off.
  Instead: `<aihio-switch aria-label="Weekly summary"></aihio-switch>`
- `aihio-table` - avoid `<aihio-table> <table> <thead> <tr> <th scope="col">Invoice</th> <th scope="col" data-numeric>Amount</th> </tr> </thead> <tbody> <tr> <th ...` (aihio lint: table-accessible-name) - An unnamed table is announced only as "table". On a narrow screen it also scrolls inside a box with no name. Give it a <caption>, or point aria-labelledby at the heading above it.
  Instead: `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col">Invoice</th> <th scope="col" data-numeric>Amount</th> </tr> </thead> <tbody> <tr> <th scope="row">INV-1042</th> <td data-numeric>€1,250.00</td> </tr> </tbody> </table> </aihio-table>`
- `aihio-table` - avoid `<aihio-table> <table> <caption>Invoices</caption> <tr> <td><strong>Invoice</strong></td> <td><strong>Amount</strong></td> </tr> <tr> <td>...` (aihio lint: table-header-cells) - Bold text in an ordinary cell looks like a header but is not one. A screen reader moving down a column cannot say which column it is in. Put the column names in <th> cells in a <thead>.
  Instead: `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col">Invoice</th> <th scope="col" data-numeric>Amount</th> </tr> </thead> <tbody> <tr> <th scope="row">INV-1042</th> <td data-numeric>€1,250.00</td> </tr> </tbody> </table> </aihio-table>`
- `aihio-table` - avoid `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col" onclick="sortBy('invoice')">Invoice</th> <th scope="col" o...` (aihio lint: table-click-handler) - A header that sorts on click cannot be reached with the keyboard, and never says which way it sorts. data-sortable gives the header a real button, sets aria-sort, and announces the new order.
  Instead: `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col" data-sortable="invoice">Invoice</th> <th scope="col" data-sortable="amount" data-numeric>Amount</th> </tr> </thead> <tbody> <tr> <th scope="row">INV-1042</th> <td data-numeric>€1,250.00</td> </tr> </tbody> </table> </aihio-table>`
- `aihio-table` - avoid `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col">Invoice</th> <th scope="col" data-numeric>Amount</th> </tr...` (aihio lint: table-click-handler) - A row that opens on click is not a link. The keyboard cannot reach it, a screen reader does not announce it, and it cannot be opened in a new tab. Link the cell that names the row.
  Instead: `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col">Invoice</th> <th scope="col" data-numeric>Amount</th> </tr> </thead> <tbody> <tr> <th scope="row"><a href="/invoices/1042">INV-1042</a></th> <td data-numeric>€1,250.00</td> </tr> </tbody> </table> </aihio-table>`
- `aihio-table` - avoid `<aihio-table> <aihio-table-header> <aihio-table-row> <aihio-table-head>Invoice</aihio-table-head> </aihio-table-row> </aihio-table-header...` (aihio lint: unknown-component) - There are no row or cell components. aihio-table wraps a native <table>, whose rows and cells are what a screen reader navigates and what the HTML parser keeps in order.
  Instead: `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col">Invoice</th> </tr> </thead> <tbody> <tr> <td>INV-1042</td> </tr> </tbody> </table> </aihio-table>`
- `aihio-table` - avoid `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col" sortable>Invoice</th> <th scope="col" sortable data-numeri...` (aihio lint: table-sortable-header) - Nothing reads a sortable attribute on a <th>. The table reads data-sortable, and makes that header a sort button.
  Instead: `<aihio-table> <table> <caption>Invoices</caption> <thead> <tr> <th scope="col" data-sortable>Invoice</th> <th scope="col" data-sortable data-numeric>Amount</th> </tr> </thead> <tbody> <tr> <th scope="row">INV-1042</th> <td data-numeric>€1,250.00</td> </tr> </tbody> </table> </aihio-table>`
- `aihio-tabs` - avoid `<aihio-tabs> <aihio-tab value="a">A</aihio-tab> <aihio-tab-panel value="a">Content</aihio-tab-panel> </aihio-tabs>` (aihio lint: missing-required-child) - aihio-tab must sit inside an aihio-tab-list; keyboard navigation is bound to the list.
  Instead: `<aihio-tabs value="a"> <aihio-tab-list> <aihio-tab value="a">A</aihio-tab> </aihio-tab-list> <aihio-tab-panel value="a">Content</aihio-tab-panel> </aihio-tabs>`
- `aihio-tabs` - avoid `<aihio-tabs value="a"> <aihio-tab-list> <aihio-tab value="a">A</aihio-tab> </aihio-tab-list> <aihio-tab-panel value="b">Content</aihio-ta...` (aihio lint: tabs-value-pairs) - Panel value does not match any tab value; the panel will never activate.
  Instead: `<aihio-tabs value="a"> <aihio-tab-list> <aihio-tab value="a">A</aihio-tab> </aihio-tab-list> <aihio-tab-panel value="a">Content</aihio-tab-panel> </aihio-tabs>`
- `aihio-toggle` - avoid `<aihio-toggle><aihio-button>B</aihio-button></aihio-toggle>` (aihio lint: forbidden-descendant) - Nested interactives. Use aihio-toggle alone — it already renders a button.
  Instead: `<aihio-toggle aria-label="Bold">B</aihio-toggle>`
- `aihio-toggle` - avoid `<aihio-toggle pressed="false">Bold</aihio-toggle>` (aihio lint: boolean-attribute-value) - Boolean attributes toggle on presence. Omit the attribute for unpressed; write `pressed` for pressed.
  Instead: `<aihio-toggle>Bold</aihio-toggle>`
- `aihio-toggle` - avoid `<span>Email notifications</span> <aihio-toggle pressed>Enabled</aihio-toggle>` (aihio lint: toggle-state-name) - The toggle is named by its state and the visible label is not associated with it, so assistive technology announces "Enabled, toggle button, pressed" with no idea what is enabled. An on/off setting is a switch: use aihio-switch in aihio-field, which also submits with the form.
  Instead: `<aihio-field> <label slot="label">Email notifications</label> <aihio-switch name="email-notifications" checked></aihio-switch> </aihio-field>`

## Semantic Token Vocabulary

When markup needs CSS of its own, read these custom properties rather than raw values. Write each name exactly as listed: `--aihio-<group>-<name>`, lowercase and hyphenated. Colours are full values (`color: var(--aihio-color-muted-fg)`) and theme themselves; for transparency, use `color-mix(in oklch, var(--aihio-color-page-fg) 40%, transparent)`.

### color

Themed colours, named for what they colour. Each has a light and a dark value.

- `--aihio-color-border-subtle` - Default border color for surfaces and outlines.
- `--aihio-color-control-highlight-bg` - Hover and pressed highlight background for neutral controls.
- `--aihio-color-control-highlight-fg` - Foreground on hover and pressed highlight backgrounds.
- `--aihio-color-destructive-bg` - Destructive emphasis background and error color.
- `--aihio-color-destructive-fg` - Foreground on destructive emphasis backgrounds.
- `--aihio-color-destructive-text` - Destructive text and iconography drawn directly on page or surface backgrounds, where the emphasis background is not used.
- `--aihio-color-field-border` - Default border color for editable form fields.
- `--aihio-color-focus-ring` - Focus indication ring for keyboard interactions.
- `--aihio-color-muted-bg` - Muted fill for grouped controls and subdued surfaces.
- `--aihio-color-muted-fg` - Supporting foreground on muted fills.
- `--aihio-color-overlay-bg` - Surface background for transient overlays such as dropdowns.
- `--aihio-color-overlay-fg` - Foreground on transient overlays.
- `--aihio-color-overlay-highlight-bg` - The highlighted item inside an overlay: the active combobox option or a hovered dropdown item. It has to differ from overlay-bg, which control-highlight-bg does not in the dark theme.
- `--aihio-color-overlay-scrim` - Modal backdrop scrim behind blocking overlays.
- `--aihio-color-page-bg` - Default page canvas and neutral active backgrounds.
- `--aihio-color-page-fg` - Primary foreground on the page canvas.
- `--aihio-color-placeholder-bg` - Fill for something that stands in for missing content, such as an avatar's initials. One step off both the page and a surface, where muted-bg matches the surface and disappears into a card.
- `--aihio-color-placeholder-fg` - Text on the placeholder fill.
- `--aihio-color-primary-action-bg` - Default filled action background.
- `--aihio-color-primary-action-fg` - Foreground on default filled actions.
- `--aihio-color-secondary-action-bg` - Lower-emphasis filled action background.
- `--aihio-color-secondary-action-fg` - Foreground on lower-emphasis filled actions.
- `--aihio-color-success-bg` - Emphasis background for confirmed and healthy states.
- `--aihio-color-success-fg` - Foreground on success emphasis backgrounds.
- `--aihio-color-success-text` - Success text and iconography drawn directly on page or surface backgrounds.
- `--aihio-color-surface-bg` - Raised surface background for cards, alerts, and dialog panels.
- `--aihio-color-surface-fg` - Primary foreground on raised surfaces.
- `--aihio-color-warning-bg` - Emphasis background for states that need attention but are not failures.
- `--aihio-color-warning-fg` - Foreground on warning emphasis backgrounds. Dark, because an accessible amber fill is light in both themes.
- `--aihio-color-warning-text` - Warning text and iconography drawn directly on page or surface backgrounds.

### spacing

Gaps and padding, named for the rhythm they set rather than a step on the scale.

- `--aihio-spacing-cluster-gap-tight` - Tight spacing inside grouped interactive controls and menu chrome.
- `--aihio-spacing-control-gap` - Default inline gap between icons, labels, and grouped actions.
- `--aihio-spacing-field-padding-block` - Block padding for default form fields.
- `--aihio-spacing-field-padding-block-lg` - Block padding for spacious form fields.
- `--aihio-spacing-field-padding-block-sm` - Block padding for compact form fields.
- `--aihio-spacing-field-padding-inline` - Inline padding for default form fields.
- `--aihio-spacing-field-padding-inline-lg` - Inline padding for spacious form fields.
- `--aihio-spacing-field-padding-inline-sm` - Inline padding for compact form fields.
- `--aihio-spacing-form-field-gap` - Recommended gap between labels, help text, and form controls.
- `--aihio-spacing-stack-lg` - Large separation for footer action areas and major section breaks.
- `--aihio-spacing-stack-md` - Standard section spacing within surfaced components.
- `--aihio-spacing-stack-sm` - Short vertical separation between related blocks such as alert content and panels.
- `--aihio-spacing-stack-tight` - Compact vertical rhythm for headings with short supporting copy.

### radius

Corner rounding for surfaces and controls.

- `--aihio-radius-interactive` - Default corner radius for buttons, inputs, toggles, and menus.
- `--aihio-radius-interactive-compact` - Tighter radius for dense interactive children such as tabs and menu items.
- `--aihio-radius-pill` - Fully rounded presentation for badges, avatars, and pill-shaped affordances.
- `--aihio-radius-surface` - Corner radius for cards, alerts, and other framed surfaces.

### font-family

Typefaces for interface text and fixed-width data.

- `--aihio-font-family-body` - Interface and body typeface for every component and page surface.
- `--aihio-font-family-code` - Typeface for code samples, markup, token names, and other fixed-width data.

### font-size

Text sizes for body copy, controls, and surface titles.

- `--aihio-font-size-badge` - Dense label size for badges and tiny metadata chips.
- `--aihio-font-size-body` - Default document body text size.
- `--aihio-font-size-body-sm` - Supporting text size for descriptions and secondary copy.
- `--aihio-font-size-control` - Default text size for controls.
- `--aihio-font-size-control-lg` - Larger text size for prominent controls.
- `--aihio-font-size-control-sm` - Compact text size for small controls.
- `--aihio-font-size-heading` - Large surface title size. Sized to lead a card, not to headline a page — a title much larger than this stops reading as part of the surface it sits on.
- `--aihio-font-size-heading-sm` - Compact surface title size for dialogs and smaller panels.

### font-weight

Weights for body text, controls, and headings.

- `--aihio-font-weight-badge` - Dense emphasis weight for badges.
- `--aihio-font-weight-body` - Default document body weight.
- `--aihio-font-weight-control` - Emphasized but compact weight for controls and inline titles.
- `--aihio-font-weight-heading` - Strong surface-heading weight.

### letter-spacing

Optical tracking, tightening as type grows.

- `--aihio-letter-spacing-body` - Default body and control tracking. Slightly negative, because the system stack sets a touch wide at UI sizes.
- `--aihio-letter-spacing-display` - Tracking for large display type, where glyph gaps open up as size grows.
- `--aihio-letter-spacing-heading` - Tracking for surface titles and section headings.
- `--aihio-letter-spacing-label` - Positive tracking for the one place it earns its keep: small capitalised labels.

### line-height

Line heights for readable copy and compact labels.

- `--aihio-line-height-body` - Default readable line height for body and field text.
- `--aihio-line-height-compact` - Tight line height for headings, labels, and short control text.

### shadow

Elevation for surfaces and overlays.

- `--aihio-shadow-modal` - Deeper elevation for blocking modal dialogs.
- `--aihio-shadow-overlay` - Elevation for popovers and transient overlays.
- `--aihio-shadow-surface` - Subtle elevation for surfaced content.

### duration

Motion timing for feedback and overlay entrance. Collapsed to 1ms under reduced motion, except the spinner.

- `--aihio-duration-feedback` - Default control feedback transition duration.
- `--aihio-duration-feedback-fast` - Fast hover and menu-item feedback transitions.
- `--aihio-duration-overlay` - Entrance timing for larger blocking overlays.
- `--aihio-duration-spinner` - One full rotation of an indeterminate busy indicator.
