# Targeting elements

Every element carries its intent as data-intent, so analytics, tests and A/B tools can find it by purpose. Never style with it: looks come from the registry.

## What is on the element

Every Prongs component puts its intent on the element it renders, as `data-intent`. It is there in production too, so anything that needs to find an element can find it by what it is for.

```tsx
<Action intent="action.delete" onClick={remove}>Delete</Action>

// renders
<button type="button" class="…" data-intent="action.delete">Delete</button>
```

To keep intent names out of production HTML, set `intentAttribute` to `'development'`.

```ts
// components/prongs/config.ts
export const intentAttribute: IntentAttribute = 'always'; // 'development' or 'never'
```

## Analytics without tracking ids

Events named by purpose need no ids added by hand, and they mean the same thing on every page. They also share their names with the rest of the app: the press on [`action.delete`](https://www.prongs.dev/docs/intents/action/delete.md), the [`conflict.state`](https://www.prongs.dev/docs/intents/conflict/state.md) error it can return and the [`audit.deleted`](https://www.prongs.dev/docs/intents/audit/deleted.md) entry it writes are all intents, so a dashboard can join them without a mapping table.

```ts
// npx prongs add analytics
import { trackIntents } from '@/components/prongs/analytics';

// Every press, submit, toggle, open and close, named by intent: { type: 'press', intent: 'action.delete', within: [...] }
trackIntents((event) => posthog.capture(`intent ${event.type}`, event));
```

`prongs add analytics` copies the helper into your components; you decide where events go. [Read the analytics guide](https://www.prongs.dev/docs/analytics.md).

## Tests and experiments

Selectors on classes break when the look changes, and selectors on text break when the copy does. A selector on the intent only breaks when the element stops doing that job. A/B tools and heatmaps can target the same way.

```ts
// Playwright: survives a redesign, because the purpose does not change when the look does.
await page.locator('[data-intent="action.delete"]').first().click();
await expect(page.locator('[data-intent="feedback.error"]')).toBeVisible();
```

## Never for styling

A selector on `data-intent` would be a second place that decides how something looks, outside the registry, and nothing would keep the two in step. `prongs check` reports it, in stylesheets and in classes (`intent-selector`). If something needs to look different, it is a different intent.

```tsx
/* prongs check: intent-selector */
[data-intent="action.delete"] { color: red; }

<Layout intent="layout.group" className="[&_[data-intent]]:gap-0">
```

## State attributes are different

Other `data-*` attributes carry state that comes from your code, like `data-read`. The components pass them to the element, and the intent styles them with `data-[read]:` variants, so the look still lives in the registry.

```tsx
<Text intent="heading.item" data-read={item.read ? '' : undefined}>{item.title}</Text>

// registry: the intent styles its own state
'heading.item': { binding: { classes: '… data-[read]:font-normal' } },
```
