> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Attributes

> DOM attributes Anvil2 writes for structure, end-to-end tests, and analytics.

Anvil2 writes three `data-*` attributes on its elements. The library owns `data-anv` and `data-anv-part`. You own `data-tracking-id` when you pass one, and you own the instance id on the component root.

## `data-anv`

`data-anv` is a library-owned marker. A button root is `data-anv="button"`. A dialog root is `data-anv="dialog"`. Nested pieces use their own names, such as `data-anv="dialog-header"`.

The library uses these names for styles and for keyboard focus queries. Do not pass `data-anv` yourself. A value you pass replaces the library value and can break those styles and queries. Do not use `data-anv` as an end-to-end test selector. `data-anv-part` is that contract.

A custom nested Menu trigger is the exception. Spread the `data-anv` value the menu provides onto that trigger so the item keeps menu styling and stays in the keyboard order. The [Menu](https://anvil.servicetitan.com/docs/components/menu/code) page describes that case.

## `data-anv-part`

`data-anv-part` names an interactive sub-element so a test can target it. The library owns the part names. You supply the instance id.

Pass `data-cy` or `data-testid` on the component. Anvil2 forwards that attribute to the root. Scope the part query to that root.

```tsx theme={null}
<MultiSelectField data-cy="tag-picker" label="Tags" />
```

```ts theme={null}
cy.get('[data-cy="tag-picker"] [data-anv-part="input"]').type("urgent");
```

`data-anv` and `data-anv-part` can sit on the same element. `data-anv` says which Anvil2 element it is. `data-anv-part` says which testable role it plays, such as `input`, `trigger`, or `option`.

Part names are unique within one component. The same name can appear on a different component. Scope the query with your instance id, or with the component's `data-anv` value.

Some parts render in a portal, outside the component root. A query scoped to your `data-cy` does not reach them. Target those parts by `data-anv-part` on the document. Each component page lists its part names and calls out portal exceptions.

When one part repeats, the element also carries a discriminator such as `data-id` or `data-anv-column-id`. Use the discriminator to pick one row, option, or cell.

## `data-tracking-id`

Button, form fields, links, menus, and other components that opt in write a `data-tracking-id`. Fullstory and other analytics tools read that attribute from the DOM.

The lookup is asynchronous. The attribute is missing on the first render.

The value is `_${scope}_${component}_` plus a short cipher of the props that component includes. The same component name, the same payload, and the same scope always produce the same id. Two controls that share those inputs share an id. Each component chooses its own payload. When the payload includes visible text, a translation changes the id.

Without `AnvilProvider` or `TrackingProvider`, generated ids use the scope `ST`.

Pass `data-tracking-id` to supply your own value. Anvil2 skips generation for that element and keeps the value you passed.

Opt out when the product already tracks with its own ids. Set `trackingIdData` on `AnvilProvider`.

```tsx theme={null}
import { AnvilProvider } from "@servicetitan/anvil2";

export function App({ children }) {
  return (
    <AnvilProvider trackingIdData={{ optOut: true }}>{children}</AnvilProvider>
  );
}
```

`trackingIdData.scope` is the prefix on generated ids. It defaults to `ST`. `TrackingProvider` accepts the same `scope` and `optOut` when the tree is not wrapped in `AnvilProvider`.

Opt-out skips later lookups. An id already written on that element stays until the component mounts again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.