Markup & Actions
The web SDK reads the page the way assistive technology does: element roles, accessible names, and states (disabled, checked, aria-selected, aria-expanded), plus which modal is open. If a screen reader can tell what a control is and what it's called, so can the agent. Most apps need no extra markup at all.
The attributes below add precision when the defaults are ambiguous.
How controls are identified
For each element, the SDK picks the first of these that's present:
data-appilots-iddata-testid(ordata-test-id)idnamearia-label- the accessible name (label text,
<label for>,aria-labelledby, or the button's text)
Field ids in your manifest should match what the page exposes. Matching is forgiving about case, accents, and separators, so the label "Model year" matches model-year. Password fields, however, only match by their exact name.
Attributes
| Attribute | Purpose |
|---|---|
data-appilots-id | The id the agent uses, checked before everything else. Use it when data-testid is already taken. |
data-appilots-action | Points a control at a declared action. Essential for list rows. |
data-appilots-skip | Hides a subtree from the agent entirely. |
data-appilots-sensitive="true" | Masks a field's value even if it isn't type="password". |
data-appilots-list-id | Marks a list container and names it. |
data-appilots-list-item | Marks a row. |
data-appilots-item-key | A row's stable domain id. |
data-appilots-item-index | A row's 0-based index in the full data set (for virtualized lists). |
data-appilots-item-count | The full data-set size when the DOM only holds one page of it. |
Declared actions
Web actions run automatically by default, so the SDK needs to know which ones must stop for the user's confirmation. Declare them in the screens option:
const SCREENS = [
{
name: 'VehicleList',
actions: [{ id: 'delete-vehicle', label: 'Delete vehicle', requiresConfirmation: true }],
},
];
A list makes this harder: every row has its own delete button, each needs a unique id, and all of them are the same declared action. data-appilots-action connects them:
{
vehicles.map((v) => (
<li key={v.id} data-appilots-list-item data-appilots-item-key={v.id}>
{v.model}
<button data-testid={`delete-${v.id}`} data-appilots-action="delete-vehicle">
Delete
</button>
</li>
));
}
Every row's button inherits requiresConfirmation from delete-vehicle, so pressing any of them shows a confirmation card first. Also mark these actions in the manifest ("effect": "destructive", "requiresConfirmation": true). The server then enforces the confirmation too, so a bypassed client can't skip it.
Sensitive data
- Password fields and fields marked
data-appilots-sensitive="true"never leave the page. Their values are replaced with<hidden>before anything is sent. data-appilots-skipremoves a subtree entirely. Use it for private notes, other people's data, or anything the agent has no reason to read.- Use
root(see Runtime options) to limit the agent to one region of the page.
What the agent sees
Each turn, the SDK captures the current screen:
import { captureSnapshot } from '@appilots/web-sdk';
captureSnapshot();
// { route, texts, inputs, buttons, toggles, sliders, lists,
// choiceGroups, elements, loading, modalOpen }
You don't need to call this; it's exported for debugging. Things worth knowing:
- Each element is exactly one kind — text, input, slider, toggle, or button.
- A row's content stays on its row. Lists aren't flattened into the top-level arrays, so the agent knows which "Delete" belongs to which item.
- While a modal is open, its contents are marked as in the modal. The agent won't press anything behind it, because such a press would silently do nothing.
- Hidden elements are left out. That includes
display: none,hidden,aria-hidden, andinert.
How actions run
The agent can navigate, press, fill forms, toggle, scroll, and long-press. Some behaviors you may notice:
- Controlled React inputs work. The SDK sets values the way React tracks them and then fires the events React listens for, so your
onChangeruns. The same applies to Vue'sv-modeland plain listeners. - Submitting waits for your app. After a submit, the SDK waits up to 6 seconds while the submit button is disabled or the page shows a loading state. A new
role="alert"oraria-live="assertive"message is treated as the submit's error. Show errors in an alert region, otherwise the agent may report success when nothing was saved. - File inputs need the user. Browsers don't let scripts choose files, so the agent asks the user to pick the file instead of pretending it did.
- Navigation is checked. After navigating, the SDK waits for the URL and screen to settle before the agent continues, so it doesn't act on the previous page.