# What an intent is

An intent is a named purpose: one entry in your registry that says what something is for, what it resolves to, and how to tell it apart from its neighbours. A button, a failure and a log line are all described the same way.

## Anatomy

An intent looks the same whatever it describes. A button and a failure are both one entry in the registry:

```ts
'action.delete': {
  kind: 'semantic',
  description: 'Permanently destroys data',
  refs: ['control.destructive'],
  keywords: ['delete', 'destroy', 'erase'],
  notFor: [
    { context: 'closing or hiding something', instead: 'action.dismiss' },
    { context: 'stopping a task without deleting anything', instead: 'action.cancel' },
  ],
},
```

```ts
'conflict.duplicate': {
  kind: 'semantic',
  description: 'It would create something that exists: a taken handle or email, a second follow',
  refs: ['status.conflict'],
  binding: { publicMessage: 'That already exists.' },
  keywords: ['taken', 'exists', 'already', 'duplicate'],
},
```

**id**: `domain.meaning`, with an optional third part for a structural variant ([`field.password.shown`](https://www.prongs.dev/docs/intents/field/password-shown.md)). The domain says what kind of thing it is, and which profile gives it meaning.

**description**: one sentence about what it is for. Required, because it is what people and agents choose by.

**refs** and **binding**: what it resolves to. A semantic intent inherits from its refs in order, then layers its own binding on top. For UI that is classes, an element, a role and input behaviour; for errors a status and what the caller sees; for logs a level and a shape.

**notFor**: when not to use it, and what to use instead. These links are what make a set of intents teachable.

**keywords**: words code uses for it. `check` uses them to spot an intent that disagrees with its own handler, label or message.

## Two layers: meaning and binding

Call sites only ever use *meaning* intents, named for the job: [`action.save`](https://www.prongs.dev/docs/intents/action/save.md), [`conflict.duplicate`](https://www.prongs.dev/docs/intents/conflict/duplicate.md), [`audit.created`](https://www.prongs.dev/docs/intents/audit/created.md). They refer to shared intents that hold the binding: looks for UI ([`control.*`](https://www.prongs.dev/docs/intents/ui.md#control), [`typography.*`](https://www.prongs.dev/docs/intents/ui.md#typography)), statuses for errors ([`status.*`](https://www.prongs.dev/docs/intents/errors.md#status)), levels for logs ([`level.*`](https://www.prongs.dev/docs/intents/logs.md#level)).

```ts
// Shared bindings, never used at a call site.
'control.destructive': { kind: 'semantic', refs: ['control.base'], binding: { classes: 'bg-destructive ...' } },
'status.conflict':     { kind: 'primitive', binding: { status: 409, expose: 'message' } },

// Meaning, what call sites use, named for the job.
'action.delete':      { kind: 'semantic', refs: ['control.destructive'], ... },
'conflict.duplicate': { kind: 'semantic', refs: ['status.conflict'], ... },
```

This is where reuse happens, and where you change your mind. Twenty actions can share four or five looks, and changing [`control.primary`](https://www.prongs.dev/docs/intents/ui.md#control-primary) restyles every action built on it; changing [`status.internal`](https://www.prongs.dev/docs/intents/errors.md#status-internal) changes every 500. Meanwhile each call site stays specific about what it does. Shared intents are rejected at call sites, by the types and by `check`.

## Each profile has its own way in

```tsx
<Action intent="action.delete" onClick={removeProject}>Delete project</Action>  // UI: a component
return fail('conflict.duplicate', 'That handle is already taken.');            // errors: fail()
log('audit.created', 'Account created', { userId: user.id });                   // logs: log()
```

UI intents render through components, and the intent picks the component and the element: [`heading.page`](https://www.prongs.dev/docs/intents/heading/page.md) is an `<h1>` through [`<Text>`](https://www.prongs.dev/docs/components/text.md), [`action.delete`](https://www.prongs.dev/docs/intents/action/delete.md) a `<button>` through [`<Action>`](https://www.prongs.dev/docs/components/action.md), [`layout.list`](https://www.prongs.dev/docs/intents/layout/list.md) a `<ul>` through [`<Layout>`](https://www.prongs.dev/docs/components/layout.md). Only [`<Layout>`](https://www.prongs.dev/docs/components/layout.md) takes a `className`, and only for layout and spacing; the escape hatch, `unsafeClassName`, needs a written reason and is counted by `check`. The [components reference](https://www.prongs.dev/docs/components.md) has the rest.

Error intents go through [`fail()`](https://www.prongs.dev/docs/profiles/errors.md), which returns the response with the intent’s status and message and writes the log entry it names. Log intents go through [`log()`](https://www.prongs.dev/docs/profiles/logs.md), which writes one line in a known shape at the intent’s level. Both helpers are copied into your repo, like the components.

## In UI: state and data, without styling

```tsx
<Action intent="nav.view" pressed={tab === 'following'}>Following</Action>
<Action intent="nav.page" href="/explore" current={path === '/explore'}>Discover</Action>
<Action intent="choice.color" vars={{ '--choice-color': color }} label={name} icon={null} />
```

State is a prop, not a style. `pressed` and `current` set `aria-pressed` and `aria-current`, and the intent styles them with Tailwind’s `aria-*` variants. Other state goes through `data-*` attributes (`data-read`, `data-compact`). Values that come from data, like a colour a user picked, go through `vars` as CSS custom properties.
