Skip to content

docs(ai-knowledge-base): add on-demand reference for development for AI Assistants - #13909

Open
hinzzx wants to merge 3 commits into
mainfrom
ai-kb-docs
Open

docs(ai-knowledge-base): add on-demand reference for development for AI Assistants#13909
hinzzx wants to merge 3 commits into
mainfrom
ai-kb-docs

Conversation

@hinzzx

@hinzzx hinzzx commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Overview

As a part of the AI Initiative (Skills, Plugins, etc.), we are building a catalog of skills, plugins, and tools that teams can discover and leverage in the projects.

But not everything that guides an AI is a skill or a tool. Some of it is plain instruction: conventions, guardrails, and hard-won facts the assistants needs to write correct code in a given repo.

This contribution is that (second) kind. It is a knowledge base for the UI5 Web Components that AI assistants can load, so the AI produces more efficient and accurate output instead of re-deriving the same facts on every task.

What we add

A new ai-knowledge-base/ folder that documents how code is actually written in the project. It is a plain set of reference files, not a plugin, so an AI assistant (Claude, Cursor, Copilot) or a human reading it gets the same guidance.

The entry point is INDEX.md, that the AGENTS.md file points to.

It carries the non-negotiable rules and a routing table that maps a task ("adding a property", "writing a test", "CSS and theming") to the one reference file that covers it.

The references sit under ai-knowledge-base/references/ and split by concern: API design, component anatomy, core rules, testing, theming, accessibility, i18n, performance, and creating a new component.

What it helps with

It captures the failure modes that no linter reports and that cost real time to rediscover. An event with no doc block is silently private. A class doc block with no @class tag is skipped with no error. A boolean property that defaults to true is rejected by the manifest generator with a misspelled message.

These are the traps that send someone (or the AI Assistant) digging through the framework. They are now written down once.

It also settles the questions that come up on every change. Which mechanism fits, a property or a slot or a method. How to name a boolean so its default is false. Which JSDoc tags pass validation and in what shape. What each lifecycle hook is for.

That means it reduces the reasoning an AI assistant has to do, and the number of wrong guesses it makes, until it comes to the right solution/conclusion. Therefore a cheaper models could potentially be used, for the same level of accuracy of the outputs as models previously needed, that were using higher reasoning effort.

How it saves tokens and time

An assistant without such context explores the codebase to re-derive these facts or search from a compressed memory on every task. It greps for how events are declared, finds two decorator styles, and guesses. That exploration burns tokens and often lands on the wrong or semi-wrong answer.

With the knowledge base, that work collapses into reading one short reference. The load-on-demand design means a typical task pulls the index plus a instruction file/s, a few thousand tokens, rather than the full corpus.

The net effect is fewer tokens spent, fewer wrong guesses, and changes that match the patterns the team already follows instead of the legacy ones scattered through the tree.

Accuracy is the one thing this depends on. The concrete claims were verified against current source. They should be re-checked periodically, since specific references drift as unrelated code changes.

- fewer tokens, fewer wrong guesses, less time exploring, while:
+ more efficient & accurate output

@hinzzx
hinzzx temporarily deployed to netlify-preview August 11, 2026 12:21 — with GitHub Actions Inactive
@hinzzx
hinzzx requested a review from a team August 11, 2026 12:22
@sap-ui5-webcomponents-release

Copy link
Copy Markdown

@hinzzx
hinzzx requested review from GDamyanov and removed request for a team August 11, 2026 13:05
@hinzzx
hinzzx temporarily deployed to netlify-preview August 11, 2026 13:09 — with GitHub Actions Inactive
@hinzzx
hinzzx temporarily deployed to netlify-preview August 14, 2026 07:40 — with GitHub Actions Inactive
@@ -0,0 +1,205 @@
# Testing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This topic is covered in the Cypress tests skill. So this file is redundant

| 27 | Logical CSS direction properties | `margin-left`, `text-align: left` | `margin-inline-start`, `text-align: start` |
| 28 | Real events in specs | `.click()`, `.type()` | `.realClick()`, `.realType()` |
| 29 | Never wait a fixed number of milliseconds | `cy.wait(300)` | assert the condition and let Cypress retry |
| 30 | Descriptive names in samples and test pages | `mgr`, `da`, `q`, `asc` | `itemManager`, `dateA`, `searchQuery`, `isAscending` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rules 25, 26, 28 and 29 are redundant.


## Structural

| # | Rule | Wrong | Right |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding a rule: Use onEnterDOM/onExitDOM — not connectedCallback/disconnectedCallback — to register and deregister external listeners (ResizeHandler.). This pattern is used consistently across Breadcrumbs, CheckBox, AvatarGroup, StepInput, and others.

## Structural

| # | Rule | Wrong | Right |
|---|------|-------|-------|

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest adding: Every document.addEventListener must have a matching document.removeEventListener, and both must live in onEnterDOM/onExitDOM. Button.ts, CheckBox.ts, RangeSlider.ts and SliderTooltip.ts all follow this, but it's easy to forget and not currently called out.

does not mean the codebase is already clean; it means new code must not add to the debt.

## Blocking

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The INDEX.md debugging section already mentions this (handlers that read event.target break when the event crosses a shadow boundary — use composedPath() instead), but core-rules.md has no matching rule. Since this is listed as a non-negotiable in INDEX.md, it should be a Blocking rule here too, with the wrong/right pattern: e.target as ChildElement → e.composedPath()[0] as ChildElement for events that bubble up from child components.


## One suppression flag, two phases

`_suppressInvalidation` makes `_invalidate` return early. It is set in the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it would be better to remove this.

- End-of-list detection: `IntersectionObserver`.
- Scroll and touch listeners: `{ passive: true }`.
- State for many children: one pass in the parent's `onBeforeRendering` writing plain child fields, not a `@property` per child.
- Derived objects: a key-guarded field on the instance.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without an example, neither a developer nor an AI will understand what a "key-guarded field" is. At least one sentence is needed: "Cache lazily: store null in the constructor, compute on first access, reset to null in onInvalidation when inputs change."

@vladitasev vladitasev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of ai-knowledge-base/references/api-design.md

Overall quality is high — dense, concrete, well-structured for machine consumption. Three factual/structural issues and a few language nits.


[Factual] Select.opened noAttribute claim is wrong

though it still reflects as an attribute since it isn't noAttribute

Select.opened is declared as @property({ type: Boolean }) with no noAttribute: true, so the "still reflects" part is correct. But the justification is backwards: the sentence implies that a @private property would normally not reflect, and noAttribute is the mechanism that would suppress it. That is the wrong mental model. The correct framing is: @private is a documentation annotation only; reflection is controlled by noAttribute. A reader who internalizes the current wording will think @private suppresses attributes by default.

Suggested fix:

Select tracks open state in opened (@private), so it is absent from the public API and docs. It still reflects as an attribute because @property({ type: Boolean }) does not set noAttribute: true@private is a doc-visibility flag only, not a reflection guard.


[Factual] The "4 events" description doesn't match the code structure

fires ui5-selection-change and selection-change, then repeats the pair PascalCased (ui5-SelectionChange, SelectionChange)

The actual implementation is two calls to _fireEvent, each of which independently fires ui5-{name} + {name}. The Pascal branch fires only when kebabToPascalCase(name) !== name. So the structure is 2 + 2 (conditional), not a flat 4. The description is correct in outcome for multi-word names, but misleads on how it works — which matters when a reader tries to understand no-conflict suppression: suppressing selection-change does not suppress SelectionChange; they come from separate _fireEvent calls.

Also: for a single-word name like open, kebabToPascalCase("open") = "Open", so the Pascal branch does fire — the doc is correct that 4 events fire. But the explanation "because "Open" differs from the original" understates it; what matters is that _fireEvent is called a second time with a different name, so preventing the first pair does not prevent the second.


[Structural] Cross-reference density works against the load-on-demand design

The INDEX routes agents to one file per task. But api-design.md references core-rules.md, performance.md, accessibility.md, and new-component.md at least six times without loading them. An agent following the INDEX routing will arrive here expecting a self-contained reference and hit walls. Either:

  • Inline the one-liners (e.g. the boolean-default rule is short enough to repeat here), or
  • Add a preamble: "Load alongside core-rules.md for any API task — this file covers shape, that one covers invariants."

[Language] Boolean polarity — show* is a valid pattern, not a footnote

The table lists hide*, no*, prevent*, disable* as the four prefixes, then introduces show* as "the mirror case" in a separate sentence. An agent scanning the table won't absorb the sentence and will treat show* as unlisted/questionable. Move it into the table:

Prefix For Examples
show* a non-default rendered element showSuggestions, showClearIcon

[Language] Enum section: no way to distinguish old violators from new correct code

Much of the existing code violates this… Write the correct form; do not migrate neighbours as a drive-by.

An agent reading context will see ButtonDesign.Default in a neighbour file and either copy it (wrong) or refuse to reference any enum at all (also wrong). Add one line: "If the file you are editing already uses the old pattern, follow the new form only in the code you write — do not mix styles within a single expression."


[Language] @property on a setter — missing the "when not to" signal

The section says "this is how every state property with a side effect is written" — accurate, but without a counterpoint an agent will use a setter accessor for properties that have no side effect, adding unnecessary boilerplate. One sentence closes this: "A plain field is correct when the property change needs no side effect beyond invalidation."

@didip1000 didip1000 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to be thorough and I noticed that the AI likes to take one-off cases and label them as the rules. I think we should throw another eye on everything to make sure that it didnt make any stuff up elsewhere.

|---------------------------|-----|
| Configure a value or a mode | property — `design`, `disabled`, `placeholder` |
| Put the component into a state | property — `open`, `collapsed`, `selected` |
| Supply markup, or a component the host must talk to | slot — `content`, `header`, `valueStateMessage` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the best description for how we use slots. MDN describes slots as something you "fill with your own markup" but we use it usually to compose other components.

| Supply markup, or a component the host must talk to | slot — `content`, `header`, `valueStateMessage` |
| React to something the user did | event — `click`, `selection-change` |
| Restyle an internal element | CSS part |
| Add an optional capability that carries its own API | a slotted subcomponent — see Features below |

@didip1000 didip1000 Aug 27, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not sure this works for slots too, by it's description it sounds like something you'd use extension for, like with button and toggle button

Prefer a property. A public method is justified in three cases and no others: a pure computation over
an argument (`isValidValue(value)`, `formatValue(date)`), a transient action with no resting state
(`navigateTo`, `reset`, `closeOverflow`), or handing out a DOM reference. A method that would only
flip a boolean the application already owns should be a property.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't public properties up to our discression?

(`navigateTo`, `reset`, `closeOverflow`), or handing out a DOM reference. A method that would only
flip a boolean the application already owns should be a property.

`open` is the canonical state property. It is inherited from `Popup` by `Popover`, `Dialog`, and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is "the canonical state property" supposed to mean


A component writes its own public property when a user interaction or its own lifecycle changes that
state, then fires the matching event in the same handler. This is the normal pattern, not an
exception — `CheckBox.checked`, `Panel.collapsed`, and `Input.value` all follow it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove

This is the normal pattern, not an exception — "

and just say something like "For example CheckBox.checked, Panel.collapsed, and Input.value"

name the new tag and its CSS must style the new child. `AvatarBadge` changed `Avatar.ts` and
`themes/Avatar.css`.

## 3. Types

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can probably be added to api design (what isnt already there) and removed from here.

import AvatarBadge from "./AvatarBadge.js";
```

Use this form, not the side-effect `import "./Foo.js";` the scaffolder prints. Either form registers the component (`.define()` runs on import either way), but every other entry in `bundle.esm.ts` uses the default-import form — match it for consistency.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can shorten this to just

Use import Foo from "./Foo.js" rather than import "./Foo.js" — both register the component, but the default-import form matches every other entry in bundle.esm.ts.

or completely omit the second half, I'm not sure if an explanation is really necessary for the AI, it might be of us 🤷‍♀️


## 5. Styles and theme parameters

`src/themes/Foo.css` holds structure. Any value that differs per theme belongs in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rewrite this so to imply that foo-parameters should be avoided, since most ui5 parameters used already have a value set for every current theme


## 6. Text

Every user-visible or announced string goes in `src/i18n/messagebundle.properties` with a text-type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also mention what each #XACT, #XBUT, ... mean?


## 11. Verify

Run `yarn generate` first. The component imports `./generated/themes/Foo.css.js`, which does not

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already in Agents.md, do we need it here too?

@didip1000

Copy link
Copy Markdown
Contributor

One more thing, the PR description really does not need to be that long, and while

- fewer tokens, fewer wrong guesses, less time exploring, while:
+ more efficient & accurate output

is amusing, I don't know if it's really relevant


| # | Rule | Wrong | Right |
|---|------|-------|-------|
| 1 | Enum imports: type-only when the enum is only used as a type; runtime when members are compared at runtime | `import type ButtonDesign from "./types/ButtonDesign.js"` when you write `ButtonDesign.Default` in code | `import type` for property declarations only; `import` (runtime) when comparing `=== ButtonDesign.Default` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The three "rules" for enums here could be reduced to one recomendation.

Something like

Rule Bad Good
Enum properties use template literal types design: ButtonDesign = ButtonDesign.Default design: ${ButtonDesign} = "Default"

The other two don't really belong as rules:

  • import type vs import is standard TypeScript enforced by the compiler
  • like mentioned in an earlier comment, using ButtonDesign.Transparent or "Transparent" comes down to which one you think is more readable. (maybe discuss which the teams on which one we should be using, but I'm personally for the former

|---|------|-------|-------|
| 13 | Private `@property` fields get `noAttribute: true`, unless a CSS selector reads the attribute | `@property({ type: Boolean }) _open = false` | `@property({ type: Boolean, noAttribute: true }) _open = false` |
| 14 | Non-rendered state is a plain field | `@property() _lastKey = ""` | `_lastKey = ""` |
| 15 | Fire the event after the state update | fire, then mutate | mutate, then fire |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This contadicts api-design.md lines 321–324

Lifecycle events fire first and act only if not prevented (Popup.openPopup, TabContainer.selectTab). Value and selection changes apply the change, fire, and revert if prevented.

| 31 | `#` followed by digits in a comment | review only |
| 32 | a comment that restates the line under it | review only |

## Why enums need both import styles

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This information is stated in 3 different places (from what ive seen), is it really necessary?

| `packages/main/src/themes/Foo.css` | yes, unless headless | Component styles |
| `packages/main/src/themes/base/Foo-parameters.css` | if themed | Default values for `--_ui5_foo_*` variables |
| `packages/main/src/themes/<theme>/Foo-parameters.css` | if themed | Per-theme overrides |
| `packages/main/src/themes/<theme>/parameters-bundle.css` | if themed | Per-theme aggregator, and the registration unit. Add an `@import` for your file to the theme families the component should be styled in, or the variables never load there. The `*_auto` folders (e.g. `sap_horizon_auto`) are generated composites of their light/dark siblings — never add a direct import there, only to the themes they draw from. Not every component needs every theme family; some are intentionally scoped to fewer |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like i said before, foo-parameters.css should only be used if there are completely structural differences in the component across all themes that arent/can't be assigned to existing ui5 css parameters

when adding a component

### Generated output

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this really relevant for us?


- No `src/types/` directory. Its components reuse `main`'s enums directly (e.g. `ButtonDesign`
imported from `@ui5/webcomponents/dist/types/ButtonDesign.js`) rather than declaring their own.
- Theming is Horizon-only: `src/themes/` contains only `sap_horizon*` folders, no `sap_fiori_3*`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theming is Horizon-only

This sounds like a rule, but it exists only because that's the latest supported theme

No src/types/ directory. Its components reuse main's enums directly (e.g. ButtonDesign imported from @ui5/webcomponents/dist/types/ButtonDesign.js) rather than declaring their own.

this also sounds like a rule when it's just a coincidence.

import buttonCss from "./generated/themes/Button.css.js";
```

`Button.ts` imports `ButtonDesign` as a value because it compares against enum members.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be updated when the enum "rule" discussed above is discussed with the teams

New code follows `core-rules.md`: `import type` plus string literals.

Decorator imports come from individual files here; the Table family instead imports from the barrel.
Both compile identically, but the barrel is essentially a Table-only convention — write new

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AI has decided to use barrel as a verb and a noun in multiple places and I dont think they make sense in ANY of the cases it chose. Please use a more relevant word.

@@ -0,0 +1,187 @@
# Accessibility

@unazko unazko Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orientation and framing

  1. The accessibility API section opener is missing its premise.
    "Interactive components expose this surface. Match the names exactly." doesn't answer what this surface is, who does what, or why it exists. The opening should establish: these properties are the public accessibility contract — application developers set them from outside, component developers wire them into the shadow DOM; and they exist because shadow DOM breaks native ARIA ID relationships, making a framework-level abstraction necessary.

  2. The API table has no "when to include" decision guide.
    The table describes what each property does but gives no rule for which subset a new component should expose. An AI writing a new component would add all six to be safe. Each property needs a condition:

accessibleName / accessibleNameRef — expose on interactive or landmark components that may appear without visible label text; almost always paired.
accessibleDescription / accessibleDescriptionRef — expose on complex components where supplementary description is meaningful (lists, tables, dialogs, pickers); paired.
accessibleRole — expose when the component can legitimately take on semantically distinct roles by use case, not as a general-purpose escape hatch.
accessibilityAttributes — expose when the component has interactive states or relationships its own properties can't fully express, or when it opens or controls other components.

  1. "Resolving the texts" has no guidance on which helper to use.
    The table lists four helpers without explaining when to reach for each. The distinction matters: getEffectiveAriaLabelText is the standard choice for components with a single label source; getAssociatedLabelForTexts is for form controls that can be labelled by a ; the two are combined in a getter when both apply. Without this, an AI picks one arbitrarily.

  2. "Keeping the ref texts live" doesn't explain why it's needed.
    The section drops straight into implementation without saying what breaks if you skip it. The reason matters: if a referenced element's text changes after render, the component won't re-render unless it observes that mutation. One sentence stating the consequence would make the mechanism feel purposeful rather than ceremonial.

  3. "Wiring ARIA in the template" states the rule but not the reason.
    ARIA and tabindex go on the focusable inner element, never on the host — this is a correct rule, but the reason is missing: shadow DOM hosts are largely invisible to the accessibility tree and AT reads inner shadow content. A developer who understands this won't accidentally place role on the host.

  4. "Roving tabindex with ItemNavigation" has no "when to use" framing.
    The section opens directly into constructor code. An AI has no signal for whether a new component needs this pattern. The trigger is clear: any component that presents a group of related items where arrow keys navigate between them and Tab treats the whole group as a single stop.

  5. "div with role="button"" has no framing.
    The section launches into imperatives with no context for when this pattern applies or why a native wasn't used. The reason matters: when the design requires an element that can contain block-level content or a complex layout that a native button can't hold. Without that, the section reads as an isolated implementation note.

  6. "Announcing dynamic changes" states a prohibition without the reason.
    "Use the shared live region, not a hand-rolled aria-live element" is correct but unexplained. Live regions need to be in the DOM and initialized empty before the announcement fires — a hand-rolled one created at announcement time is often silently ignored by AT. The singleton handles this correctly.

Technical gaps

  1. accessibilityAttributes narrowing is mentioned but never shown.
    The note that Pick<AccessibilityAttributes, ...> should be used is correct, but there is no example of the full property declaration — decorator, narrowed type, and default value. An AI writing a new component that needs this property will guess.

  2. The undefined / explicit value rule is incomplete and too narrow.
    The note about aria-disabled={false} rendering as a literal string is buried in the template section and covers only one attribute. The rule needs a clear two-case split:

Presence-only attributes (aria-disabled, aria-readonly, aria-label, aria-required, aria-description): return the value or undefined — never false or an empty string.
Tristate/bistate attributes (aria-checked, aria-selected, aria-expanded): return the explicit boolean or string value even when false — AT needs the explicit state.

  1. registerUI5Element has no trigger guard, and accessibleDescriptionRef is treated as second-class.
    registerUI5Element should only be called when the component uses accessibleNameRef or accessibleDescriptionRef. Without that guard, an AI may add it to every component with an accessible name. Separately, accessibleDescriptionRef should receive equal coverage throughout this section — the helpers getEffectiveAriaDescriptionText and getAllAccessibleDescriptionRefTexts exist and the List pattern handles both refs together.

  2. accessibilityInfo getter is referenced but never defined.
    "Expose an accessibilityInfo getter so containers can describe a slotted child" — no interface, no use case, no example. Should reference the AccessibilityInfo type from packages/base/src/types.ts, explain when it's needed (components that may be slotted into containers like ui5-table or ui5-list that synthesize custom screen reader announcements), and show a minimal getter.

  3. announce lacks when-to-use guidance.
    The section explains the API and the Polite / Assertive distinction but not when to call it. Add: call it inside event handlers fired by user interaction when UI state changes dynamically and that change isn't already expressed by a newly focused element. ColorPicker's _togglePickerMode and List's selection confirmation are the canonical patterns.

  4. Disabled state "non-native role" framing is imprecise.
    Reframe as: use aria-disabled (and remove from tab order) on any element where the native disabled attribute is not supported — non-form elements and custom element hosts. The phrase "non-native role" misses the real condition.

Checklist

  1. The delegatesFocus: true item contradicts the Focus section.
    The item is unconditional, but the Focus section says it's only needed for a few components. Fix the item to be conditional ("if the host has no focusable element in the natural tab order"), and add a sentence explaining why: without it, programmatic element.focus() from the application hits the host with no tabindex and focus is silently lost.

Gaps vs docs/2-advanced/09-accessibility.md

  1. tooltip property is absent.
    The docs show it maps to the native title attribute on the inner focusable element. Should note: bind tooltip to title on the focusable inner element, never on the host — a title on the custom element host produces redundant or incorrect AT output.

  2. Testing guidance is missing.
    The docs name JAWS 2025 + Chrome as the reference environment and Access Assistant for HTML/ARIA validation. The checklist says "no automated tooling — coverage means a Cypress test" but gives no manual testing baseline. A sentence pointing to the reference environment would close the gap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants